how to do pagination in vuejs - vue.js

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));

Related

Vue class components dynamically add component depending on answer from backend

So from the backend I get a array of objects that look kind of like this
ItemsToAdd
{
Page: MemberPage
Feature: Search
Text: "Something to explain said feature"
}
So i match these values to enums in the frontend and then on for example the memberpage i do this check
private get itemsForPageFeatures(): ItemsToAdd[] {
return this.items.filter(
(f) =>
f.page== Pages.MemberPage &&
f.feature != null
);
}
What we get from the backend will change a lot over time and is only the same for weeks at most. So I would like to avoid to have to add the components in the template as it will become dead code fast and will become a huge thing to have to just go around and delete dead code. So preferably i would like to add it using a function and then for example for the search feature i would have a ref on the parent like
<SearchBox :ref="Features.Search" />
and in code just add elements where the ItemsToAdd objects Feature property match the ref
is this possible in Vue? things like appendChild and so on doesn't work in Vue but that is the closest thing i can think of to kind of what I want. This function would basically just loop through the itemsForPageFeatures and add the features belonging to the page it is run on.
For another example how the template looks
<template>
<div class="container-fluid mt-3">
<div
class="d-flex flex-row justify-content-between flex-wrap align-items-center"
>
<div class="d-align-self-end">
<SearchBox :ref="Features.Search" />
</div>
</div>
<MessagesFilter
:ref="Features.MessagesFilter"
/>
<DataChart
:ref="Features.DataChart"
/>
So say we got an answer from backend where it contains an object that has a feature property DataChart and another one with Search so now i would want components to be added under the DataChart component and the SearchBox component but not the messagesFilter one as we didnt get that from the backend. But then next week we change in backend so we no longer want to display the Search feature component under searchbox. so we only get the object with DataChart so then it should only render the DataChart one. So the solution would have to work without having to make changes to the frontend everytime we change what we want to display as the backend will only be database configs that dont require releases.
Closest i can come up with is this function that does not work for Vue as appendChild doesnt work there but to help with kind of what i imagine. So the component to be generated is known and will always be the same type of component. It is where it is to be placed that is the dynamic part.
private showTextBoxes() {
this.itemsForPageFeatures.forEach((element) => {
let el = this.$createElement(NewMinorFeatureTextBox, {
props: {
item: element,
},
});
var ref = `${element.feature}`
this.$refs.ref.appendChild(el);
});
}
You can use dynamic components for it. use it like this:
<component v-for="item in itemsForPageFeatures" :is="getComponent(item.Feature)" :key="item.Feature"/>
also inside your script:
export default {
data() {
return {
items: [
{
Page: "MemberPage",
Feature: "Search",
Text: "Something to explain said feature"
}
]
};
},
computed: {
itemsForPageFeatures() {
return this.items.filter(
f =>
f.Page === "MemberPage" &&
f.Feature != null
);
}
},
methods: {
getComponent(feature) {
switch (feature) {
case "Search":
return "search-box";
default:
return "";
}
}
}
};

How come is my array not reactive in vuejs?

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: []

VueJS: computed property using another computed property isn't working correctly

this is not these: post1, post2
i'm building a small SPA and so i started with this basic principle that i tested and built up into the code farther below that i'm having problems with. it works ONLY if i call the secondary property in the DOM (why??) and i've included it to illustrate where i started from, hoping that the principles would translate as i built up.
JS:
var app = new Vue({
el: '#app',
computed: {
today: function() {
return new Date().getTime();
},
tomorrow: function() {
return this.today + (1*24*60*60*1000);
},
});
DOM:
<div id='app'>
<p>{{ today }}</p>
<p>{{ tomorrow }}</p>
</div>
so. that works. again, ONLY if app.tomorrow is called in the DOM. (wish i knew how NOT to need that...) BUT my problem is this next part, which i based on this starter code. it DOESN'T work:
JS:
var app = new Vue({
el: '#app',
data: {
dueNow: 0,
reports: { 90: [], 'DN': [] },
},
computed: {
today() {
return new Date().getTime();
},
tMinus30() {
return new Date().getTime() - (30 * 24 * 60 * 60 * 1000);
},
dueNow() {
var count = 0;
$.each(this.rows, function(index, obj) {
var workingDate = new Date(obj.dateSubmitted).getTime();
console.log('workingDate: '+workingDate);
console.log('today: '+this.today); <-- console reports 'undefined'!?
console.log('today: '+this.tMinus30); <-- console reports 'undefined'!?
if(workingDate < this.today && workingDate > this.tMinus30){
console.log('knock, knock: due NOW'); <-- therefore this never fires
count++;
console.log('count: '+count);
// build reports array - doesn't work, but that's for another day...
this.reports['DN']['count'] = push(count);
this.reports['DN'][index]['reportID'] = push(obj.id);
this.reports['DN'][index]['assetID'] = push(obj.assetid);
}
});
return count;
},
}
});
DOM:
<div id='app'>
<div class="col-lg-2">
<h1 class="text-center pt-2">NOW</h1>
<p>{{ today.getDay() }}</p>
<p class="img-text mb-1" #click="expandInfo('DN')">{{ dueNow }}</p>
</div>
</div>
so not only am i not getting the count of rows that are dueNow, i'm not getting today.getDay() calculated either, which, based on this from VueJS, should work. i suspect this is a flying nightmare, but i don't see it... it DOES iterate over the rows properly. it DOES calculate the working date for each row. but nothing else works. i thought i understood VueJS well enough to attempt this. and i even built it up from the first example... but i'm at a loss as to what's broken.
what principle am i overlooking to make this gel? it feels like some concept has eluded me.
EDIT: i also perused this article but don't know that it applies as there's nothing that isn't declared in the data already...
EDIT: OK! :) i need to clarify. when i say "it doesn't work", what i mean is the computed property tomorrow does not render and reports the other computed property in the console as "undefined", despite it (today) rendering just fine (without the additional method). my expected/desired result is that i will see a number indicating how many rows passed muster and are dueNow and i want to see the today rendered as "Monday" and tomorrow as "Tuesday" (for example). this last should be interchangeable with other date functions. if possible, i'd also like to have the array reports update properly... but that's out of scope right now.

Invert boolean on click with v-for?

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;
}
}

VueJS 2 - Submitting form with Stripe Checkout response?

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".