Beginner to JS and VueCLI so I'll try to explain as best as I can. I'm using Express as my back-end.
I'm trying to change the boolean in my array of objects on click. I'm able to accomplish that but when I click on a different list item in my v-for loop it's flipping the boolean in all other indexes of my array. Here's my code:
Express: /routes:
// fake data store
const tasks = [
{ id: 1, task: 't1', completed: false},
{ id: 2, task: 't2', completed: false},
{ id: 3, task: 't3', completed: false}
];
/**
* GET handler for /tasks route
* #returns {Array.<{id: Number, task: String, completed: Boolean}>} array of task objects
*/
router.get('/', (req, res) => {
res.send(tasks);
});
Webapp:
/**
* GET /tasks
* #returns Promise => {Array.<{id: Number, task: String, completed: Boolean}>} array of task objects
*/
export function getTasks() {
return request('tasks');
}
and now my Vue component:
<template>
<div id="tasks">
<h2>Movies to Add</h2>
<ul class="todo-list">
<li v-for='task in tasks' :id="task.id" v-on:click="completeMovie($event)" :key='task.id' class="todo-list__li">
<input class="todo-list__input" type="checkbox" :name='task.task' :id="task.task">
<div class="todo-list__checkbox">
<span class="todo-list__checkbox-inner"><i></i></span>
</div>
<label :for='task.task'>{{ task.task }}</label>
</li>
</ul>
</div>
</template>
<script>
import {getTasks} from './../../services/tasks';
export default {
name: 'TaskList',
data: function() {
return {
tasks: []
};
},
created: function() {
getTasks()
.then(res => this.tasks = res);
},
methods: {
completeMovie: function (event) {
var taskId = event.currentTarget.id -1;
getTasks()
.then((res) => {
this.tasks = res;
res[taskId].completed = !res[taskId].completed;
});
}
}
}
</script>
So when I click on my first list item it changes the Task: t1 to True but if I click on the second list item it changes t1 back to False and t2 to True. I'm not sure exactly what I'm doing wrong. I'm not even sure this is the best way to do this. My main issue is I'm not sure why it's happening.
Any help is much appreciated!
You're probably over-complicating this.
All you need is
<li v-for="task in tasks" :key="task.id"
#click="task.completed = !task.completed"
class="todo-list__li">
Demo ~ https://jsfiddle.net/xy1q0auL/2/
There's no (obvious) need to re-fetch the tasks every time you click on one. This is why your previous changes are reset; it's because you overwrite all the data with the unmodified values from getTasks().
When completeMovie() is called, you send an HTTP request to your server that is gonna send you back unaltered task list. I don't understand clearly what's you're trying to achieve but your callback in the promise has no sens. In the callback you reaffect the "tasks" list in your vue :
In this case, here is the event timeline you have to do :
When client page is loaded, Vue Initialized, the vue component calls your webservice to get the task list then print it.
Then when I click on a task, you have to make a another HTTP call to your webservice (on another route), that will update the task list and will return it.
Then in the call of your vue component you reaffect the new task list.
You can make its simply:
<li v-for='task in tasks' :id="task.id" v-on:click="completeMovie(task.id)" :key='task.id' class="todo-list__li">
<input class="todo-list__input" type="checkbox" :name='task.task' :id="task.task">
<div class="todo-list__checkbox">
<span class="todo-list__checkbox-inner"><i></i></span>
</div>
<label :for='task.task'>{{ task.task }}</label>
</li>
Ans for the method
completeMovie: function ($taskId) {
this.tasks[$taskId].completed = !this.tasks[$taskId].completed;
}
}
Related
Good evening everyone,
I have been making a kind of social network as a personal project using vuejs, nodejs and mysql database.
Basically, you can post a message, and then people can answer to it. I bind comments to post using an id. I got two tables: 1 comments and 1 posts. If a comment is posted for post number 38, in mysql table there is a field idPost = 38.
i got a function displaying all the answers for the post by clicking on a button, which is:
displayAnswers(id) {
axios.get('http://localhost:3000/wall/answer/get/'+id )
.then(response => {
this.answers = response.data.resultat;
})
.catch(error => {
console.log(error);
})
}
Where id is the id of the post I want to display answers.
Now, the problem is when I add a comment, I need either to refresh the page to see the comment or to force the refresh by calling the displaypost function, like this:
postAnswer(id) {
let syntaxe = /^[a-z A-ZáàâäãåçéèêëíìîïñóòôöõúùûüýÿæœÁÀÂÄÃÅÇÉÈÊËÍÌÎÏÑÓÒÔÖÕÚÙÛÜÝŸÆŒ0-9-]{1,}$/;
if(syntaxe.test(this.answerToPost)) {
let answer = {
message: this.answerToPost,
postId: id,
auteur: this.$store.state.pseudoUser
}
axios.post('http://localhost:3000/wall/post/answer', answer)
.then(response => {
console.log(response);
this.feedbackMessage = response.data.message;
this.isAlert = false;
this.answerToPost = '';
setTimeout(() => {
this.feedbackMessage = ''
}, 2000);
this.displayAnswers(id);
})
.catch(error => {
console.log(error);
this.feedbackMessage = error.response.data.message;
this.isAlert = true;
})
} else {
this.errorMessage = "Le message ne respecte pas la syntaxe autorisée";
return;
}
},
To summarize, my data this.answers, is not reactive. it is declared this way in the app:
data() {
return {
Auteur: '',
displayPostAnswers: [],
answerToPost: '',
isAlert: true,
feedbackMessage: '',
answers: ''
}
},
and called this way in my template, using a v-for loop to display the answers:
<div v-for="answer in answers" :key="answer.id" class="answerDisplayer" >
<div class="containerEachAnswer">
<div class="avatarAuteur">
<img src="../assets/defaultUser.png" width="48" height="48" alt="">
</div>
<div class="answer">
<span>
<strong>{{ answer.auteur }}</strong><br>
{{ answer.message}}
</span>
</div>
</div>
</div>
</div>
I looked for the issue on the internet, I found this doc: https://fr.vuejs.org/v2/guide/reactivity.html.
So I tried to use the function Vue.set but it does not seem to work.
I would like to know if more experienced developer could help me to find another way to either make my data reactive or help me to do it another way, I tried several kind of things but it did not work.
PS: I tried to use computed data, but v-for does not work with computed data.
Thank you!
Have a good evening!
Since you are trying to change this within the instance I suggest you try this.$set(this.someObject, 'b', 2) as described in https://fr.vuejs.org/v2/guide/reactivity.html#Pour-les-objects
Also you seem to declare answers as a string in your data function, try declaring it as an array answers: []
I am building an app using Node.js and Vue.
My DATA for the component is the following:
data() {
return {
campaign: {
buses: [],
weeks: [
{
oneWayBuses: [],
returnBuses: []
}
]
},
busesMap: {
// id is the bus ID. Value is the index in the campaign.buses array.
},
};
},
I fill the buses and weeks array in MOUNTED section in two separate methods after getting the data from the server:
responseForWeeks => {
responseForWeeks.forEach(
week => this.campaign.weeks.push(week);
)
}
responseForBuses => {
responseForBuses.forEach(
bus => this.campaign.buses.push(bus);
// Here I also fill the busesMap to link each week to its bus index in the array of buses
this.busesMap[bus.id] = this.campaign.buses.length - 1;
)
}
So the idea is that my busesMap looks like busesId keys and index values:
busesMap = {
'k3jbjdlkk': 0,
'xjkxh834b': 1,
'hkf37sndd': 2
}
However, when I try to iterate over weeks, v-if does not update so no bus info is shown:
<ul>
<li
v-for="(busId, index) in week.oneWayBuses"
:key="index"
:item="busId"
>
<span v-if="campaign.buses[busesMap.busId]">
<strong>{{ campaign.buses[busesMap.busId].busLabel }}</strong>
leaves on the
<span>{{ campaign.buses[busesMap.busId].oneWayDepartureDate.toDate() | formatDate }}</span>
</span>
</li>
</ul>
On the other side, if I shorten the v-if condition to campaign.buses, then I get into the condition but campaign.buses[busesMap.busId] is still undefined, so I get an ERROR trying to display busLabel and oneWayDepartureDate
I've read vue in depth documentation, but couldn't come up with a resolution.
Any gotchas you can find out?
Try this:
async mounted(){
await responseForWeeks
await responseForBuses
responseForWeeks => {
responseForWeeks.forEach(
week => this.campaign.weeks.push(week);
)
}
// this is partial since it is what you provided
responseForBuses => {
responseForBuses.forEach(
bus => this.campaign.buses.push(bus);
// Here I also fill the busesMap to link each week to its bus index in the array of buses
this.busesMap[bus.id] = this.campaign.buses.length - 1;
)
}
}
Basically you want to make sure that before your component loads your data is in place. You can also create computed properties which will force re rendering if dependencies are changed and they are in the dom.
Actually, the problem was indeed in the HTML.
When trying to access the object keys, better use [] intead of a dot .
Final HTML result would be as follows:
<ul>
<li
v-for="(busId, index) in week.oneWayBuses"
:key="index"
:item="busId"
>
<span v-if="campaign.buses[[busesMap[busId]]]">
<strong>{{ campaign.buses[busesMap[busId]].busLabel }}</strong>
leaves on the
<span>{{ campaign.buses[busesMap[busId]].oneWayDepartureDate.toDate() | formatDate }}</span>
</span>
</li>
</ul>
What was happening is that previously campaign.buses[busesMap.busId] did not exist, thus not rendering anything. Solved to campaign.buses[busesMap[busId]]. Also used claudators for the displayed moustache sintach.
Hope it helps someone else messing with Objects!
I'm attempting to integrate VueJS (2.4.2) with Stripe Checkout, but my form is not submitting the updated values for the token and email returned from Stripe.
The basic flow: mount a Vue instance to a form, select a "plan" from a JSON object, open the Stripe Checkout modal populated with the plan's info, bind a couple of the form inputs to the values returned by Stripe, and submit the form. All goes according to plan EXCEPT that the data that actually hits the server is NOT the updated values.
I have tried v-bind and v-model and neither seems to work. I can see the form being updated with the correct values from the Stripe response, but when it actually submits, the originally bound data is submitted.
The HTML (Laravel Blade)
#extends('layouts.master')
#section('page_meta')
<title>{{ page_title('Checkout') }}</title>
#endsection
#section('content')
<div class="container">
#include('errors.list')
{!! Form::open([
'url' => '/subscriptions',
'id' => 'checkoutForm',
]) !!}
#foreach ($plans as $plan)
<div>
{{ $plan->name }} {{ $plan->description }}
<button v-on:click.prevent="subscribe({{ $plan->id }})">Select</button>
</div>
#endforeach
<input name="selected_plan" :value="selectedPlanId">
<input name="stripe_email" :value="stripeEmail">
<input name="stripe_token" :value="stripeToken">
{!! Form::close() !!}
</div>
#endsection
#push('scripts.body')
<script>
var plans = {!! $plans !!}; // JSON from the controller
</script>
<script src="https://checkout.stripe.com/checkout.js"></script>
<script src="/js/checkout.js"></script>
#endpush
The JavaScript:
var vm = new Vue({
el: "#checkoutForm",
data: {
plans: plans, // From a global JSON array
selectedPlan: null, // Default value
stripeEmail: 'foo#example.com', // Initial bindings to test values
stripeToken: 'invalidToken' // Initial bindings to test values
},
computed: {
selectedPlanId() {
if (this.selectedPlan) {
return this.selectedPlan.id;
}
return '';
}
},
methods: {
subscribe(planId) {
let plan = this.findPlanById(planId);
console.log(plan); // Works as expected
this.selectedPlan = plan;
// The following opens a Stripe checkout widget
// with all the correct information.
this.handler.open({
name: plan.name,
description: plan.description,
amount: plan.price * 100, // stored as decimal
token: (token) => {
console.log(token); // Works as expected
this.stripeToken = token.id; // Verified in Vue Dev Tools
this.stripeEmail = token.email; // Verified in Vue Dev Tools
alert(this.stripeToken); // Correct values
alert(this.stripeEmail); // Correct values
// At this point, the form inputs are updated
// with the correct values returned from Stripe.
vm.$el.submit(); // Submits the form to the proper URL
// When the POST request hits the server, the
// token and email fields have their original values
// i.e. "foo#example.com" and "invalidToken"
}
});
},
findPlanById(id) {
return this.plans.find(plan => plan.id == id);
}
},
created() {
this.handler = StripeCheckout.configure({
key: window.Laravel.stripeKey,
locale: 'auto',
});
}
})
UPDATE: It works if I wrap the form submission in a setTimeout. Apparently it needs a little time for the updated values to "take".
hi i want to do pagination in my view page.can anyone tell me how to do that in vuejs..
Here is my view page:
<div class="container">
<div class="row">
<el-row :gutter="12">
<el-col>
<p>View Candidates</p>
</el-col>
</el-row>
<el-row :gutter="12">
<template v-for="c in candidates">
<el-col :span="6">
<Candidate :c="c" :key="c.id"></Candidate>
</el-col>
</template>
</el-row>
</div>
here is my js page:
const app = new Vue({
el: '#app',
data() {
return {
candidates: window.data.candidates,
}
},
components: { Candidate }
});
i am working on laravel5.4 and vuejs 2
Please can anyone help me..how to do this..
For real pagination you will need to ensure that your endpoints (from your post I'd say something like /candidates) will return json AND that it will return a pagniated object ofcourse.
In Laravel you'd do it like
public function index() {
return Candidates::paginate(10);
}
EDIT: for more information regarding laravel pagination you can take a look at their examples and docs: https://laravel.com/docs/5.4/pagination
A full example is rather hard to give but here a really short one
routes/web.php
Route::get('candidates', 'CandidateController#index');
app/http/controller/CandidateController
public function index() {
$candidates = App\Candidate::paginate(10);
return $candidates;
}
For a more detailed version of the laravel part you should provide your Controller, Migration, Routing setup.
In Vue I'd suggest you load all your data from within Vue and not with blade. Even though you could keep it as it is - it would be more "unified".
data: function() {
return { paginator: null }
},
created: function() {
// load initial first 10 entries
axios.get('/candidates').then(function(response) {
this.paginator = response.data;
}.bind(this));
}
Ok so now you have the initial load as you had it before. You can loop through pagniator.data which is your actual list now. Small example:
<ul v-if="paginator"><!-- important !not loaded on initial render-->
<li v-for="paginator.data as candidate">{{ candidate.name }}</li>
</ul>
Now to the load more. Let's say you want a button for that. The paginator has a pro called next_page_url to give you the next http endpoint. If it's null - now data is left to load.
<button v-if="paginator && paginator.next_page_url" #click.prevent="loadMore">Load more</button>
Button is setup - now the load more
methods: {
loadMore: function() {
// load next 10 elements
axios.get(this.paginator.next_page_url).then(function(response) {
// you have two options now. Either replace the paginator fully - then you will actually "page" your results.
// only 10 will be visible at any time
this.paginator = response.data;
}.bind(this));
}
}
There you go this is an actual pagination. If you want to loadMore to add 10 elements to your current list it is a little bit more tricky because you don't want to replace the paginator.data with the new loaded stuff. You want to concat it.
...
axios.get(this.paginator.next_page_url).then(function(response) {
response.data.data = this.paginator.data.concat(response.data.data);
this.paginator = response.data;
}.bind(this));
I'm using Vue JS 2.0 and this is a table I'm trying to display. Everything displays properly initially but when I update(in a method called editPlayerStat) editedPlayers[any-Player-Id], it doesn't update the interpolated value in the respective small tag.
At the editPlayerStat() I check to see if editedPlayers[any-Player-Id] is changing properly and it is. But for some reason it isn't getting updated in the small tag. Also, in the span tag's v-if doesn't update and has the same problem.
While in the same method editPlayerStat(), I manually update player.stats.month.goals you see interpolated, to see if also has the same problem and IT DOESN'T. It works just fine.
What am I doing wrong?
<tr v-if="showMonth" v-for="player in players">
<td><input type="checkbox"></td>
<td>{{ player.name }}</td>
<td><a #click.prevent="editPlayerStat(player._id,'goals',false)" href=""><i class="fa fa-arrow-down"></i></a>
{{ player.stats.month.goals }}
<a #click.prevent="editPlayerStat(player._id,'goals',true)" href=""><i class="fa fa-arrow-up"></i></a>
<span id="changed"> <small>({{ editedPlayers[player._id].stats.goals }})</small></span>
</td>
<td><a #click.prevent="editPlayerStat(player._id,'assists',false)" href=""><i class="fa fa-arrow-down"></i></a>
{{ player.stats.month.assists }}
<a #click.prevent="editPlayerStat(player._id,'assists',true)" href=""><i class="fa fa-arrow-up"></i></a>
<span id="changed" v-if="editedPlayers[player._id].stats.assists > 0"> <small>({{ editedPlayers[player._id].stats.assists }})</small></span>
</td>
</tr>
This is my editPlayerStat() method:
editPlayerStat(id, type, isIncrease) {
console.log('editPlayerStat...');
console.log('editedPlayers[player._id].stats.goals...' + this.editedPlayers[id].stats.goals );
if(isIncrease) {
console.log('increase');
this.editedPlayers[id].stats[type]++;
this.editedPlayers[id].isEdited = true;
} else if(!isIncrease && this.editedPlayers[id].stats[type] > 0) {
console.log('decrease');
this.editedPlayers[id].stats[type]--;
this.editedPlayers[id].isEdited = true;
}
}
ANOTHER instance:
I use v-model on an input and display the interpolated value beside and it updates just fine when I update the input tag value. But when I 'watch' for changes in the object player, the watch function doesn't run except for the first time.
<input class="form-control" id="name" placeholder="Name" type="text" v-model="player.name">{{ player.name }}
watch: {
player: function(player) {
console.log('from watch... '+JSON.stringify(player));
}
},
methods: {
getPlayerInfo() {
var vm = this;
axios.get('http://localhost:3000/admin/players/info/'+'5847a093a5e13203a4d0455f')
.then(function (response) {
vm.player = response.data;
console.log(response);
console.log(JSON.stringify(vm.player));
console.log('showForm: ' + vm.showForm);
})
.catch(function (error) {
console.log(error);
});
},
created: {
this.getPlayerInfo();
}
Output:
Object { data: Object, status: 200, statusText: "OK", headers: Object, config: Object, request: XMLHttpRequest }
{"_id":"5847a093a5e13203a4d0455f","name":"Michael","age":25,"dateOfBirth":"03-04-1998","phone":7987997799,"image":"imgur::djnwkndkjnkn","preferredFoot":"Right","isActive":true,"positions":["CB","RB","RW"]}
showForm: true
from watch... {"_id":"5847a093a5e13203a4d0455f","name":"Michael","age":25,"dateOfBirth":"03-04-1998","phone":7987997799,"image":"imgur::djnwkndkjnkn","preferredFoot":"Right","isActive":true,"positions":["CB","RB","RW"]}
As you can see, even though the interpolated player.name is updated when I edit the value in the input tag, the watch function for player property doesn't run except for that first time when player was initialized with an Object.
Thanks to #vbranden and participants in an issue I raised here. So here's what was going wrong.
I was running into this behavior in Vue 2.0, which according to the documentation, "cannot detect property addition or deletion". You will run into this sooner or later.
The thing was that, I was adding properties to an Object directly (or as I thought 'string indexed array' values to an array. Note: also see issue; javascript doesn't allow associative arrays) and Vue couldn't watch for changes in it due to this caveat.
Solution: Instead of directly assigning properties to an Object, you need to use Vue.set() or this.$set() to assign them. This allows Vue to track for changes in them.
Original Problem: fiddle
editedPlayers: []
this.editedPlayers['someID'] = { name: 'John', stats: { goals: 10, assists: 10 }};
this.editedPlayers['someOtherID'] = { name: 'Flint', stats: { goals: 10, assists: 10 }};
Solution: fiddle
editedPlayers: {}
Vue.set(this.editedPlayers, 'someID', { name: 'John', stats: { goals: 10, assists: 10 }});
Vue.set(this.editedPlayers, 'someOtherID', { name: 'Flint', stats: { goals: 10, assists: 10 }});