Vue-select not loading more when searching - laravel

So I made a simple v-select where i put an infinite scroll. This works good I can load all the users and when i scroll down 10 more users are added to the array. When I typ i can filter in the select and i get to see 10 users filtered but when I scroll down there are no 10 users added. I only see loading more options. I have been searching for this quit some time but haven't found an answer for this problem so I thought I try to ask it here...
The only thing I noticed debugging is when I console.log(this.$refs.load)
I see :
<li data-v-299e239e class="loader"> Loading more options...</li>
But when i search nothing is logged so i guess it must be something with the observer or so...
If u need more info please ask.
my code
vue component:
<template>
<v-select
:options="users"
label="name"
:filterable="false"
#open="onOpen"
#close="onClose"
#search="inputSearch"
class="form-control"
:loading="loading"
>
<template #list-footer>
<li v-show="hasNextPage" ref="load" class="loader">
Loading more options...
</li>
</template>
</v-select>
</template>
<script>
import 'vue-select/dist/vue-select.css';
import _ from "lodash";
export default {
name: 'InfiniteScroll',
data: () => ({
observer: null,
limit: 10,
search: '',
users: [],
total: 0,
page: 0,
loading: false,
}),
computed: {
hasNextPage() {
return this.users.length < this.total
},
},
mounted() {
this.observer = new IntersectionObserver(this.infiniteScroll)
},
created() {
this.getUsers();
},
methods: {
getUsers(search) {
this.page++;
axios
.get('users', {
params: {
search: search,
page: this.page,
}
})
.then((response) => {
this.users = this.users.concat(response.data.data);
this.total = response.data.total;
})
.catch()
.then(() => {
this.loading = false;
})
},
async onOpen() {
if (this.hasNextPage) {
await this.$nextTick()
console.log(this.$refs.load)
this.observer.observe(this.$refs.load)
}
},
onClose() {
this.observer.disconnect()
},
async infiniteScroll([{isIntersecting, target}]) {
if (isIntersecting) {
const ul = target.offsetParent
const scrollTop = target.offsetParent.scrollTop
// this.limit += 10
this.getUsers();
await this.$nextTick()
ul.scrollTop = scrollTop
}
},
inputSearch: _.debounce( async function (search, loading) {
if (search.length) {
this.users = []
this.loading = true
this.page = 0
this.getUsers(search, loading)
//await this.$nextTick()
}
}, 500),
},
}
</script>
<style scoped>
.loader {
text-align: center;
color: #bbbbbb;
}
</style>
UserController :
public function users(Request $request){
return User::query()
->when($request->search,function ($q) use ($request) {
$q->where('name', 'like', '%' . $request->search . '%');
})
->orderBy('name', 'ASC')->paginate(10);
}

I've fixed the issue with adding two code lines simply.
...
inputSearch: _.debounce( async function (search, loading) {
if (search.length) {
this.users = []
this.loading = true
this.page = 0
this.getUsers(search, loading)
//await this.$nextTick()
// Add following code lines for reloading the infiniteScroll observer
this.onClose()
await this.onOpen()
}
}, 500),
...

Related

Show component only after all images loaded

I am using Vue 3 and what i would like to achieve is to load all images inside a card (Album Card) and only then show the component on screen..
below is an how it looks now and also my code.
Does anybody have an idea how to achieve this?
currently component is shown first and then the images are loaded, which does not seem like a perfect user experience.
example
<template>
<div class="content-container">
<div v-if="isLoading" style="width: 100%">LOADING</div>
<album-card
v-for="album in this.albums"
:key="album.id"
:albumTitle="album.title"
:albumId="album.id"
:albumPhotos="album.thumbnailPhotos.map((photo) => photo)"
></album-card>
</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import albumCard from "#/components/AlbumCard.vue";
interface Album {
userId: number;
id: number;
title: string;
thumbnailPhotos: Array<Photo>;
}
interface Photo {
albumId: number;
id: number;
title: string;
url: string;
thumbnailUrl: string;
}
export default defineComponent({
name: "Albums",
components: {
albumCard,
},
data() {
return {
albums: [] as Album[],
isLoading: false as Boolean,
};
},
methods: {
async getAlbums() {
this.isLoading = true;
let id_param = this.$route.params.id;
fetch(
`https://jsonplaceholder.typicode.com/albums/${
id_param === undefined ? "" : "?userId=" + id_param
}`
)
.then((response) => response.json())
.then((response: Album[]) => {
//api returns array, loop needed
response.forEach((album: Album) => {
this.getRandomPhotos(album.id).then((response: Photo[]) => {
album.thumbnailPhotos = response;
this.albums.push(album);
});
});
})
.then(() => {
this.isLoading = false;
});
},
getRandomPhotos(albumId: number): Promise<Photo[]> {
var promise = fetch(
`https://jsonplaceholder.typicode.com/photos?albumId=${albumId}`
)
.then((response) => response.json())
.then((response: Photo[]) => {
const shuffled = this.shuffleArray(response);
return shuffled.splice(0, 3);
});
return promise;
},
/*
Durstenfeld shuffle by stackoverflow answer:
https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array/12646864#12646864
*/
shuffleArray(array: Photo[]): Photo[] {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
},
},
created: function () {
this.getAlbums();
},
});
</script>
What i did to solve this problem was using function on load event on (img) html tag inside album-card component. While images are loading a loading spinner is shown. After three images are loaded show the component on screen.
<template>
<router-link
class="router-link"
#click="selectAlbum(albumsId)"
:to="{ name: 'Photos', params: { albumId: albumsId } }"
>
<div class="album-card-container" v-show="this.numLoaded == 3">
<div class="photos-container">
<img
v-for="photo in this.thumbnailPhotos()"
:key="photo.id"
:src="photo.thumbnailUrl"
#load="loaded()"
/>
</div>
<span>
{{ albumTitle }}
</span>
</div>
<div v-if="this.numLoaded != 3" class="album-card-container">
<the-loader></the-loader>
</div>
</router-link>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { store } from "#/store";
export default defineComponent({
name: "album-card",
props: {
albumTitle: String,
albumsId: Number,
},
data: function () {
return {
store: store,
numLoaded: 0,
};
},
methods: {
thumbnailPhotos() {
return this.$attrs.albumPhotos;
},
selectAlbum(value: string) {
this.store.selectedAlbum = value;
},
loaded() {
this.numLoaded = this.numLoaded + 1;
},
},
});
</script>
Important note on using this approach is to use v-show instead of v-if on the div. v-show puts element in html(and sets display:none), while the v-if does not render element in html so images are never loaded.

How to instant data refresh with Laravel and vue.js?

I work with constantly changing api data. I use Laravel and Vue.js. There is a steady stream of data when I control the network with F11. But it has no effect on the DOM.
Here are sample codes. I would be glad if you help.
HTML code;
<div class="row">
<div class="col-md-12">
<p class="tv-value" v-html="totalMeetings"></p>
</div>
</div>
Script Code;
<script>
export default {
data() {
return {
totalMeetings: null
}
},
created() {
this.getPosts();
},
methods: {
getPosts() {
axios
.get("/get-total-meetings")
.then(response => (this.totalMeetings = response.data))
.catch(error => {
this.errors.push(error);
});
}
},
mounted() {
setInterval(function () {
axios
.get("/get-total-meetings")
.then(response => (this.totalMeetings = response.data))
.catch(error => {
this.errors.push(error);
});
}, 2000)
}
}
</script>
Change your setInterval function to arrow function like this.
setInterval(() => {
axios
.get("/get-total-meetings")
.then(response => (this.totalMeetings = response.data))
.catch(error => {
this.errors.push(error);
});
}, 2000);
You could put a watcher for that to be able vue to watch the changes of your data. like this.
watch: {
totalMeetings(val) {
this.totalMeetings = val
}
}
Or create a computed property for it to update the value when it changes.
computed: {
total_meetings() {
return this.totalMeetings
}
}
then your component should look like this
<p class="tv-value" v-html="total_meetings"></p>

Display passed image in template, in VUE

So I have this code:
<template>
<div id="search-wrapper">
<div>
<input
id="search_input"
ref="autocomplete"
placeholder="Search"
class="search-location"
onfocus="value = ''"
#keyup.enter.native="displayPic"
>
</div>
</div>
</template>
<script>
import VueGoogleAutocomplete from "vue-google-autocomplete";
export default {
data: function() {
return {
search_input: {},
pic: {}
};
},
mounted() {
this.autocomplete = new google.maps.places.Autocomplete(
this.$refs.autocomplete
);
this.autocomplete.addListener("place_changed", () => {
let place = this.autocomplete.getPlace();
if (place.photos) {
place.photos.forEach(photo => {
let pic=place.photos[0].getUrl()
console.log(pic);
});
}
});
},
methods: {
displayPic(ref){
this.autocomplete = new google.maps.places.Autocomplete(
this.$refs.autocomplete);
this.autocomplete.addListener("place_changed", () => {
let place = this.autocomplete.getPlace();
if (place.photos) {
place.photos.forEach(photo => {
let pic=place.photos[0].getUrl()
console.log(pic);
});
}
})
},
}
}
I want to pass the "pic" parameter, resulted in displayPic, which is a function, into my template, after one of the locations is being selected.
I've tried several approaches, but I'm very new to Vue so it's a little bit tricky, at least until I'll understand how the components go.
Right now, the event is on enter, but I would like it to be triggered when a place is selected.
Any ideas how can I do that?
Right now, the most important thing is getting the pic value into my template.
<template>
<div id="search-wrapper">
<div>
<input style="width:500px;"
id="search_input"
ref="autocomplete"
placeholder="Search"
class="search-location"
onfocus="value = ''"
v-on:keyup.enter="displayPic"
#onclick="displayPic"
>
<img style="width:500px;;margin:5%;" :src="pic" >
</div>
</div>
</template>
<script>
import VueGoogleAutocomplete from "vue-google-autocomplete";
export default {
data: function() {
return {
search_input: {},
pic:""
};
},
mounted() {
this.autocomplete = new google.maps.places.Autocomplete(
this.$refs.autocomplete,
{componentRestrictions: {country: "us"}}
);
},
methods: {
displayPic: function(){
this.autocomplete.addListener("place_changed", () => {
let place = this.autocomplete.getPlace();
if (place.photos) {
place.photos.forEach(photo => {
this.pic=place.photos[0].getUrl()
});
}
})
},
}
}
</script>

Vue custom v-model value doesn't update the value after getting it from axios get request

I have a component called "TextInput":
<template>
<q-input
v-model="content"
#input="handleInput"
color="secondary"
:float-label="floatLabel" />
</template>
<script>
import { QInput, QField } from "quasar-framework/dist/quasar.mat.esm";
export default {
props: ['floatLabel', 'value'],
data () {
return {
content: this.value
}
},
components: {
'q-field': QField,
'q-input': QInput,
},
methods: {
handleInput(e) {
this.$emit('input', this.content)
}
}
}
</script>
I used this component in another component:
<template>
<card card-title = "Edit Skill">
<img slot="img" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAJeSURBVEhLzZVPSFRRGMVnKopI+odghFjWQDD05v8/dGCEaFEhbnwIQQTVol2rCHElQog7lwm6qdy0jBJpWQvBImgTEqGYoKIDYhS4qt9n38Qb5973ni7KA2fuPd937jt35t33JrKvUCqVjmaz2XI8Hm8qFArHmT8KS/ytehk7UqlUHPOzTCbzA36EDroNbsEnQcS/zFjWy5mRy+VuYaxiHIDNWo4wl6ANlb5g/VvfIAw3ZDfQ0dJfWIKi8uE4zil6LuuKon2DEonEMZpL6XT6ipbqsDOIi12EH/AnisViC/MqfK49exCN+xheqWyAN0huNN5FOAnlF/gMh+l3Sp+5b9AUu+tT2QBvEKfwMPMemXPR28wfy7wG3yCaa8lk8rzKBniDgmANkgCa6yqN8AYx3sX/xsB+6TOag2iM0phQaYQ3CL88V+5OUrefOp6byzSq+Xz+jJaM4AC049vEf8GPcv+MQRSn4UOVRnBIcixchfN4v1r4jf4vwmTj9UGIq/BLLBY7oiUj8IyxeEilEWymG88M0yj+WQI7/nQAhV6ac4xdWjKCRXfwzMMR/MMm0nvK+BO+gCvoE7p8G1GK9yguMG4/1TYQdg2f8U3tJdd5YH1M+NrnMFRV7hoE9MhfikpfHMC8xU5Oqg4NNnmWTW7K/5WW/IFZ3lcZlaHBBgfhmMpgYH5Jzk2VocG69/C6ymBglrf3u93+fKxb5aBcUhkM13UPEjTOwu+MtYfwtbatwL8B645yKHB6TrPDNIvlxflJy1bsOagGFpf/SZDcK4JKKq0gpKtSqRxS+b8QifwGm+z/Ksto7VkAAAAASUVORK5CYII=">
<form class="clearfix" slot="form">
<bim-text v-model="skill.name" :floatLabel="input_label"></bim-text>
<q-btn
#click="edit"
icon="save"
size="14px"
color="secondary"
label="Save" />
</form>
</card>
</template>
<script>
import { QBtn } from "quasar-framework/dist/quasar.mat.esm";
import { Card, TextInput } from "../../base";
import router from '../../../routes/routes';
export default {
data: function () {
return {
id: this.$route.params.id,
skill: {
name: ''
},
input_label: 'Skill Name'
}
},
components: {
'card': Card,
'bim-text': TextInput,
'q-btn': QBtn
},
methods: {
edit: function(){
axios.patch('/api/skills/'+this.id, {
name: this.skill.name,
}, { headers: { Authorization: 'Bearer '.concat(localStorage.getItem('token')) } })
.then(response => {
alert('success');
router.push({ name: "IndexSkills"});
}).catch(error => {
console.log('dd');
});
}
},
mounted() {
axios.get("/api/skills/"+this.id, { headers: { Authorization: 'Bearer '.concat(localStorage.getItem('token')) } })
.then(response => {
this.skill = response.data.data;
}).catch(error => {
alert('error')
});
}
}
</script>
<style scoped>
.q-btn{
float: right;
margin-top: 20px;
}
</style>
As you can see, I created an skill object with empty property called name and I made an axios request to get the specified object using its id. In then function of the axios request skill should be updated but what happened was that the v-model still empty.
What would be the problem here? and how can I solve it?
Thanks in advance.
You only assign v-model value (value property) to your content variable on initialization (data() method, which is only called on component initialization). You have no code that can react to value (v-model) change, that would update the content variable.
You should create a watch for value, and then set this.content = this.value again.
PS: Also, try instead of this
this.skill = response.data.data;
do this
this.skill.name = response.data.data.name;

Dynamic content to v-expansion-panel-content

I would like to load data each time I click on header, then show the data (when it is loaded) into the v-expansion-panel-content.
It's not always working.
If user open the accordion before data is loaded, it wont be updated once data comes from server.
let Projects = {
template: `<v-expansion-panel >
<v-expansion-panel-content ripple v-for="project in filteredProjects "
:key="project.id" lazy><div slot="header"
#click="loadSpaces(project)">{{project.name}}</div>
<v-card>
<v-card-text class="grey lighten-3" >
<v-btn flat block
v-for="space in spaces" :key="space.id"
#click="loadLayout(project)">{{space.id}}</v-btn>
</v-card-text>
</v-card>
</v-expansion-panel-content>
</v-expansion-panel>`,
components: {
Spaces
},
props: {
searchFilter: String
},
data: () => ({
data: uniBuilderProjects.state,
}),
computed: {
filteredProjects: function () {
var self = this;
return self.data.projects.filter(function (project) {
return project.name.toUpperCase().indexOf(self.searchFilter.toUpperCase()) !== -1;
})
},
spaces: function () {
return this.data.spaces;
}
},
methods: {
loadSpaces: function(project){
uniBuilderProjects.loadSpaces(project.id);
}
},
mounted: function () {
// Load Projects
uniBuilderProjects.loadProjects();
this.$devices = this.$resource('https://jsonplaceholder.typicode.com/posts{/id}/comments');
}
};
Fddle
you can use 'eager' prop in v-expansion-panel-content.
and set value true.
e.g <v-expansion-panel-content eager ></v-expansion-panel-content>
I messed with your fiddle and came up with this:
let Projects = {
template: `<v-expansion-panel >
<v-expansion-panel-content ripple v-for="project in filteredProjects "
:key="project.id" lazy><div slot="header"
#click="loadSpaces(project)">{{project.name}}</div>
<v-card>
<v-card-text v-if="loading == true" class="grey lighten-3" >
<v-btn flat block
v-for="space in spaces" :key="space.id"
#click="loadLayout(project)">{{space.id}}</v-btn>
</v-card-text>
</v-card>
</v-expansion-panel-content>
</v-expansion-panel>`,
components: {
Spaces,
},
props: {
searchFilter: String,
},
data: () => ({
data: uniBuilderProjects.state,
loading: false,
}),
computed: {
filteredProjects: function() {
var self = this;
return self.data.projects.filter(function(project) {
return (
project.name
.toUpperCase()
.indexOf(self.searchFilter.toUpperCase()) !== -1
);
});
},
spaces: function() {
return this.data.spaces;
},
},
methods: {
loadSpaces: function(project) {
this.loading = false;
console.log(this.loading);
uniBuilderProjects.loadSpaces(project.id);
this.loading = true;
},
},
mounted: function() {
// Load Projects
uniBuilderProjects.loadProjects();
this.$devices = this.$resource(
'https://jsonplaceholder.typicode.com/posts{/id}/comments',
);
},
};
So I used conditional rendering and whenever the function is complete set the this.loading to true and boom. Not sure if it's the best way but seems to be a quick solution.

Resources