Inertia mounted hook with search resets pagination on every action - laravel

My vue file:
data() {
return {
search: '',
}
},
mounted() {
this.search = this.filters.search ?? '';
},
watch: {
search(value) {
Inertia.get('/contacts', { search: value }, {
preserveState: true,
replace: true,
})
}
The Laravel Controller:
$contacts = Model::query()
->paginate(10)
->withQueryString();
return Inertia::render('Contacts/List', [
'contacts' => $contacts,
'filters' => request()->only(['search']),
'currentPage' => request()->page,
]);
It all works perfectly if the mounted block is missing.
With it, on every Inertia reload a "new search" is registered (since it's changed in the mounted hook) and it returns to page 1, so basically, every time you change the page it returns you to page 1.
It should be working perfectly with the Composition API's setup, but not sure why can't I get it to work here.

I use axios instead of Inertia.js in pagination
<template>
<div class="row mt-4">
<div class="col-md-5">
<span
>showing {{ data.from }}-{{ data.to }} of
{{ data.total }} items</span
>
</div>
<div class="col-lg-7">
<nav aria-label="...">
<ul class="pagination float-end">
<template v-for="link in data.links" :key="link.id">
<li v-if="!link.url" class="page-item">
<a #click.prevent="paginate(link.label)" class="page-link">
<span
aria-hidden="true"
v-html="link.label"
></span>
</a>
</li>
<li
v-else
:class="{ active: link.active }"
class="page-item"
>
<a
#click.prevent="paginate(link.label)"
v-html="link.label"
class="page-link"
></a>
</li>
</template>
</ul>
</nav>
</div>
</div>
</template>
<script>
import axios from 'axios'
export default {
props: ["data", "url"],
methods: {
paginate(label) {
console.log(`${route(this.url)}?page=${label}`);
axios.get(`${route(this.url)}?page=${label}`).then((res) => {
this.$emit('paginate', res.data)
});
}
}
}
</script>

I ran into the same issue, and after some digging I found this answer. The gist is that you should set your search parameter directly in the data attribute instead of using mounted():
data() {
return {
search: this.filters.search ?? '',
}
},
watch: {
search(value) {
Inertia.get('/contacts', { search: value }, {
preserveState: true,
replace: true,
})
}

Related

How to disactivate the last breadcrumb in InertiaJS?

I am trying to disactivate the last crumb in InertiaJS (Laravel + Vue3) but I think I have an error or something missing somewhere. The error I am getting is the "isLast" is not defined.
isLast: This method simply takes in an index of the crumb array and checks if that crumb is the last one in the list.
selected: This is a simple method that emits an event whenever a crumb is selected. The event is then caught by Example.vue…
Here is the code:
BreadCrumb.vue
<template>
<div>
<ol class="inline-flex items-center space-x-1 md:space-x-3">
<li
v-for="(crumb, ci) in crumbs"
:key="ci"
>
<div class="inline-flex items-center">
<icon name="arrow"/>
<Link :href="`/${crumb}`" as="button" type="button" :class="{ disabled: isLast(ci) }" #click="selected(crumb)" class="capitalize" >
{{ crumb }}
</Link>
</div>
</li>
</ol>
</div>
</template>
<script>
import { Link } from '#inertiajs/inertia-vue3'
import Icon from '#/Shared/Icon'
export default {
components: {
Icon,
Link,
},
props: {
crumbs: {
type: Array,
required: true,
},
},
methods: {
isLast(index) {
return index === this.crumbs.length - 1;
},
},
}
</script>
Example.vue
<template>
<div class="md:p-12 md:pt-1">
<breadcrumb class="flex pl-4 mb-12" :crumbs="crumbs" #selected="selected"></breadcrumb>
</div>
</template>
<script>
import Breadcrumb from '#/Shared/Breadcrumb'
export default {
components: {
Breadcrumb,
},
return {
crumbs: ['home','users', 'create'],
}
methods: {
selected(crumb) {
console.log(crumb);
},
},
}
</script>
I would recommend binding the disabled-attribute
<Link ... :disabled="isLast(ci)" >
👆

How do I get the images to show in divs, not only the link. Nuxt, CocktailDB API

Im building a simple app to practise Nuxt and axios with the CocktailDB API https://www.thecocktaildb.com/api.php. I have troubble getting the images to show rather than just the links. In console.log the links also shows. How do I get the images from the API to show in the list, and not only the link? Thanks in advance!
<template>
<div>
<div>
<SearchDrink/>
</div>
<div>
<div v-for="drink in drinks" :key="drink.id">
<div class="drink">
<p> {{ drink.strDrink
}} </p>
<img src="" alt=""/> {{ drink.strDrinkThumb
}}
<p>Instructions:</p>
<p> {{ drink.strInstructions }} </p>
<div class="ing"> Ingridients:
<p> {{ drink.strIngredient1 }} </p>
<p> {{ drink.strIngredient2 }} </p>
<p> {{ drink.strIngredient3 }} </p>
<p> {{ drink.strIngredient4 }} </p>
<p> {{ drink.strIngredient5 }} </p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import SearchDrink from '../../components/SearchDrink.vue'
import axios from 'axios'
export default {
components:{
SearchDrink,
},
data(){
return {
drinks: [],
}
},
methods: {
getAllDrinks(){
axios.get('https://thecocktaildb.com/api/json/v1/1/search.php?s=')
.then((response) => {
this.drinks = response.data.drinks
const myDrink = response.data.drinks
console.log(myDrink)
console.log(myDrink.strDrinkThumb)
})
.catch((error) =>{
console.log(error)
})
},
},
created(){
this.getAllDrinks()
},
// methods: {
// searchDrink(){
// if(!this.search){
// return this.drinks
// }else{
// return this.drinks.filter(drink =>
// drink.text.toLowerCase().includes(this.search.
// toLowerCase()))
// }
// }
// },
head(){
return {
title: 'Drinks App',
meta: [
{
hid: 'description',
name: 'description',
content: 'Best place to search a Drink'
}
]
}
}
}
</script>
you're not putting the image inside the src tag.
This can be done using the v-bind directive as
v-bind:src="..." or :src=".."
Example :
new Vue({
el: "#app",
data: () => ({
drinks: []
}),
mounted(){
axios.get('https://thecocktaildb.com/api/json/v1/1/search.php?s=')
.then((response) => {
this.drinks = response.data.drinks
})
}
})
img {
height: 200px;
width: 200px
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/1.2.1/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<ul>
<li v-for="drink of drinks">
<img :src="drink.strDrinkThumb"/>
</li>
</ul>
</div>

Why does Vue evaluate my v-if to false despite it being true?

When I run this, vue returns the second template, even though groups.length is equal to 1.
Why? Does it have to do with the order in which the mounting occurs and v-if is evaluated? Again, I am certain that groups.length evaluates to 1. I have tried using beforeMount as opposed to mounted, but that did not work.
<template v-if = "groups.length">
<ul id = "groupList">
<li v-for = "group in groups">
<a>{{ group.name }}</a>
</li>
<div class = "addSidebar">
<label class = "btn" for = "modal-1">+</label>
</div>
</ul>
</template>
<template v-else>
<ul id = "groupList">
<li>
<a>You have not created/joined any groups.</a>
</li>
<div class = "addSidebar">
<label class = "btn" for = "modal-1">+</label>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
groups: {}
}
},
methods: {
getGroups() {
axios.get('groupList').then((response) => {
this.groups = response.data
}).catch((errors) => {
console.log(errors)
});
},
newModal() {
$('#modal').modal('show');
}
},
mounted() {
this.getGroups()
},
name: "groupList"
}
</script>
you need to use javascript Async
https://www.w3schools.com/js/js_async.asp
<template >
<div>
<ul id="groupList" v-if="groups.length">
<li v-for="group in groups" :key="group.id">
<a>{{ group.name }}</a>
</li>
<div class="addSidebar">
<label class="btn" for="modal-1">+</label>
</div>
</ul>
<ul id="groupList" v-else>
<li>
<a>You have not created/joined any groups.</a>
</li>
<div class="addSidebar">
<label class="btn" for="modal-1">+</label>
</div>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
groups: {},
};
},
methods: {
async getGroups() {
await axios
.get("groupList")
.then((response) => {
this.groups = response.data;
})
.catch((errors) => {
console.log(errors);
});
},
newModal() {
$("#modal").modal("show");
},
},
async mounted() {
await this.getGroups();
},
name: "groupList",
};
</script>
in your code you created 2 <template > which is not valid syntax and vue should have root element
https://v2.vuejs.org/v2/guide/components.html#A-Single-Root-Element
Change
v-if = "groups.length"
to:
v-if="groups && groups.length > 0"
And you should have a single that contains one elements in it.

Active user does not show in real time chat app

I am following up this guide https://www.codechief.org/article/real-time-chat-app-with-laravel-6-vue-js-and-pusher#gsc.tab=0 to create real-time chat app in Laravel and Vue.
But it does not show list of active user.
Also this span never shows
<span class="text-muted" v-if="activeUser" >{{ activeUser.first_name }}` is typing...</span>
Also, this method does not work properly because in console log it shows undefined is typing...
sendTypingEvent() {
Echo.join('chat')
.whisper('typing', this.user);
console.log(this.user.fist_name + ' is typing now')
}
And it is not actually real time, because I see new messages only if I reload page.
This is Vue component
<template>
<div class="row">
<div class="col-8">
<div class="card card-default">
<div class="card-header">Messages</div>
<div class="card-body p-0">
<ul class="list-unstyled" style="height:300px; overflow-y:scroll" v-chat-scroll>
<li class="p-2" v-for="(message, index) in messages" :key="index" >
<strong>{{ message.user.first_name }}</strong>
{{ message.message }}
</li>
</ul>
</div>
<input
#keydown="sendTypingEvent"
#keyup.enter="sendMessage"
v-model="newMessage"
type="text"
name="message"
placeholder="Enter your message..."
class="form-control">
</div>
<span class="text-muted" v-if="activeUser" >{{ activeUser.first_name }} is typing...</span>
</div>
<div class="col-4">
<div class="card card-default">
<div class="card-header">Active Users</div>
<div class="card-body">
<ul>
<li class="py-2" v-for="(user, index) in users" :key="index">
{{ user.first_name }}
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props:['user'],
data() {
return {
messages: [],
newMessage: '',
users:[],
activeUser: false,
typingTimer: false,
}
},
created() {
this.fetchMessages();
Echo.join('chat')
.here(user => {
this.users = user;
})
.joining(user => {
this.users.push(user);
})
.leaving(user => {
this.users = this.users.filter(u => u.id != user.id);
})
.listen('ChatEvent',(event) => {
this.messages.push(event.chat);
})
.listenForWhisper('typing', user => {
this.activeUser = user;
if(this.typingTimer) {
clearTimeout(this.typingTimer);
}
this.typingTimer = setTimeout(() => {
this.activeUser = false;
}, 1000);
})
},
methods: {
fetchMessages() {
axios.get('messages').then(response => {
this.messages = response.data;
})
},
sendMessage() {
this.messages.push({
user: this.user,
message: this.newMessage
});
axios.post('messages', {message: this.newMessage});
this.newMessage = '';
},
sendTypingEvent() {
Echo.join('chat')
.whisper('typing', this.user);
console.log(this.user.fist_name + ' is typing now')
}
}
}
</script>
"Also, this method does not work properly because in console log it shows undefined is typing..."
i assume you made a typo in your console.log, you probably meant:
this.user.first_name
Concerning your "realtime" problem, i suspect it might be because your broadcasted Event is being queued, so you might want to use the ShouldBroadcastNow instead of ShouldBroadcast

Laravel & Vue.js. I Can't Fix My Code

I'm working on an application to manage some Cvs.
All my resource functions work perfectly (creating and editing new Cvs etc.)
But in my Vue.js part, it's about showing details for every Cv in a show view using Axios to get data from the database or post using a form.
My console shows a reactivity problem about my variables: infos and info.
Here is my show.blade.php code:
#extends('layouts.app')
#section('content')
<div class ="container" id="app">
<div class="container" id="app">
<div class ="row">
<div class="col-md-12">
<div class="panel-panel-primary">
<div class="panel-heading">
<div class="row">
<div class="col-md-18"> <h3 class="panel-title"> Experience </h3></div>
<div class="col-md-2 text-right">
<button class="btn btn-success" > Ajouter</button>
</div>
</div>
</div>
<div class="panel-body" >
<ul class="list-group">
<li class="list-group-item" v-for="info in infos">
<div class="pull-right">
<button class="btn btn-warning btn-sm">Edit</button>
</div>
<h3>#{{ info.titre }}</h3>
<p>#{{ info.body }}</p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div >
<form class="flex flex-col" #submit.prevent="addExperience">
<textarea class="border rounded p-2 mp-3" v-model="info.titre"></textarea>
<textarea class="border rounded p-2 mp-3" v-model="info.body"></textarea>
<button type="submit" class="border rounded py-2">Commenter</button>
</form>
</div>
</div>
</div>
#endsection
#section('javascripts')
<script src="{{ asset('js/vue.js')}}"> </script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
window.Laravel={!! json_encode([
'csrfToken' => csrf_token(),
'idExperience' => $id,
'url' =>url('/')]) !!} ;
</script>
<script>
var app= new Vue({
el:'#app',
data:{
message: '',
infos: [],
info:{
id:0,
cv_id:window.Laravel.idExperience,
titre:'',
body:'',
}},
methods:{
getExperiences: function() {
axios.get(window.Laravel.url+'/getexperiences/'+window.Laravel.idExperience)
.then(response=>{
console.log(reponse.data);
this.info=reponse.data;})
.catch(error =>{
console.log('errors: ', error);})},
addExperience:function(){
axios.post(window.Laravel.url+'/addexperience', this.info)
.then(repsonse => {
if(response.data.etat){
this.infos.unshift(this.info);
this.info={
id:0,
cv_id:window.Laravel.idExperience,
titre:'',
body:'',
};}})
.catch(error =>{
console.log('errors: ', error)})},
created:function(){
this.getExperiences();})};
</script>
#endsection
And my two functions get Experiences and addExperience in CvController:
public function getExperiences($id){
$cv= Cv::find($id);
return $cv->infos;
}
public function addExperience(Request $request){
$info = new Info;
$info->titre=$request->titre;
$info->body=$request->body;
$info->cv_id=$request->cv_id;
$info->save();
return response()->json(['etat'=> true, 'id'=> $info->id]);
}
And these are my routes:
Route::resource('cvs','CvController');
Route::get('/getexperiences/{id}', 'CvController#getExperiences');
Route::post('/addexperience', 'CvController#addExperience');
My console:
There are some syntax errors that need to be fixed first.
There's an extra ")" at the end of your created method
The methods property is missing a closing "}"
There's a missing ")" at the very end to match the opening new Vue( parenthesis.
It's helpful to indent your code to make these things easier to spot.
Then, you'll need to update your data to be a function that returns an object. See Vue Docs - Data Must Be a Function
You also have some typos for the "response" variable.
Lastly, in your HTML you have two divs that have id="app".
Your JS should look something like:
var app = new Vue({
el: '#app',
data: {
message: '',
infos: [],
info: {
id: 0,
cv_id: window.Laravel.idExperience,
titre: '',
body: '',
}
},
methods: {
getExperiences: function () {
axios.get(window.Laravel.url + '/getexperiences/' + window.Laravel.idExperience)
.then(response => {
console.log(reponse.data);
this.info = response.data;
})
.catch(error => {
console.log('errors: ', error);
})
},
addExperience: function () {
axios.post(window.Laravel.url + '/addexperience', this.info)
.then(response => {
if (response.data.etat) {
this.infos.unshift(this.info);
this.info = {
id: 0,
cv_id: window.Laravel.idExperience,
titre: '',
body: '',
};
}
})
.catch(error => {
console.log('errors: ', error)
})
},
created: function () {
this.getExperiences();
}
}
});
Let me know if that works for you.

Resources