Display passed image in template, in VUE - image

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>

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.

Real-time search engine with VueJS and Laravel

I am doing the search engine section in VueJS and Laravel, but I have a problem that does not allow me to advance in the other sections. The search engine opens and everything but when I write it only sends the first letter or 2 but not all of them like this in this image:
image of the data you send
the data that I write
After that it shows me the following error in console:
Uncaught (in promise) NavigationDuplicated: Avoided redundant navigation to current location: "/search?q=th"
Now showing my search engine code:
<template>
<div class="form_MCycW">
<form autocomplete="off" #sumbit.prevent>
<label class="visuallyhidden" for="search">Search</label>
<div class="field_2KO5E">
<input id="search" ref="input" v-model.trim="query" name="search" type="text" placeholder="Search for a movie, tv show or person..." #keyup="goToRoute" #blur="unFocus">
<button v-if="showButton" type="button" aria-label="Close" #click="goBack">
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 15 15"><g fill="none" stroke="#fff" stroke-linecap="round" stroke-miterlimit="10" stroke-width="1.5"><path d="M.75.75l13.5 13.5M14.25.75L.75 14.25"/></g></svg>
</button>
</div>
</form>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
data() {
return {
query: this.$route.query.q ? this.$route.query.q : ''
}
},
computed: {
showButton() {
return this.$route.name === 'search';
},
...mapState({
search: state => state.event.fromPage
})
},
mounted() {
this.$refs.input.focus();
},
methods: {
goToRoute() {
if (this.query) {
this.$router.push({
name: 'search',
query: { q: this.query },
});
} else {
this.$router.push({
path: this.fromPage,
});
}
},
goBack() {
this.query = '';
this.$router.push({
path: '/',
});
},
unFocus (e) {
if (this.$route.name !== 'search') {
const target = e.relatedTarget;
if (!target || !target.classList.contains('search-toggle')) {
this.query = '';
this.$store.commit('closeSearch');
}
}
}
}
}
</script>
This is the other section of the search engine:
<template>
<main class="main">
<div class="listing">
<div class="listing__head"><h2 class="listing__title">{{ title }}</h2></div>
<div class="listing__items">
<div class="card" v-for="(item, index) in data.data" :key="index">
<router-link :to="{ name: 'show-serie', params: { id: item.id }}" class="card__link">
<div class="card__img lazyloaded"><img class="lazyload image_183rJ" :src="'/_assets/img/covers/posters/' + item.poster" :alt="item.name"></div>
<h2 class="card__name">{{ item.name }}</h2>
<div class="card__rating">
<div class="card__stars"><div :style="{width: item.rate * 10 + '%'}"></div></div>
<div class="card__vote">{{ item.rate }}</div>
</div>
</router-link>
</div>
</div>
</div>
</main>
</template>
<script>
import { mapState } from 'vuex';
let fromPage = '/';
export default {
name: "search",
metaInfo: {
bodyAttrs: {
class: 'page page-search'
}
},
computed: {
...mapState({
data: state => state.search.data,
loading: state => state.search.loading
}),
query() {
return this.$route.query.q ? this.$route.query.q : '';
},
title() {
return this.query ? `Results For: ${this.query}` : '';
},
},
async asyncData ({ query, error, redirect }) {
try {
if (query.q) {
this.$store.dispatch("GET_SEARCH_LIST", query.q);
} else {
redirect('/');
}
} catch {
error({ message: 'Page not found' });
}
},
mounted () {
this.$store.commit('openSearch');
this.$store.commit('setFromPage', fromPage);
if (this.data.length == 0 || this.data === null) {
this.$store.dispatch("GET_SEARCH_LIST", this.query);
}
setTimeout(() => {
this.showSlideUpAnimation = true;
}, 100);
},
beforeRouteEnter (to, from, next) {
fromPage = from.path;
next();
},
beforeRouteUpdate (to, from, next) {
next();
},
beforeRouteLeave (to, from, next) {
const search = document.getElementById('search');
next();
if (search && search.value.length) {
this.$store.commit('closeSearch');
}
}
};
</script>
In my routes section it is defined as follows:
{
name: 'search',
path: '/search',
component: require('../views/' + themeName + '/control/search/index').default
}
It is supposed to be a real-time search engine. I would appreciate your help in solving this problem...
What you need is a debounce. What it does is that it wait or delay till the user had finished typing before the model get updated or before you send it to the server.
An example of how it works is here
Here is a package for it.
https://github.com/vuejs-tips/v-debounce

TypeError: _this2.categoryOptions.find is not a function

I'm trying to create a select option that will show the category when it's been saved. The problem I'm having is that I'm getting this error in my console
[Vue warn]: Error in render: "TypeError: _this2.categoryOptions.find is not a function"
Here is my code
<template>
<div>
<select class="form-control" v-model="addCategory" name="category">
<option v-for="category in categoryOptions" :value="category.id">{{ category.name }}</option>
</select>
</div>
</template>
<script>
export default {
props: ['product', 'categories'],
data() {
return {
addCategory: null,
categoryOptions: []
}
},
mounted() {
axios.get('/admin/products/'+this.product.id+'/category').then((response) => {
this.categoryOptions = response.data;
});
},
computed: {
categoryOptions(){
let options = [];
options.push({id:0, text: "Please select one"});
let filteredCategory = this.categories.filter(category => {
return this.categoryOptions.find(selected => categoryOptions.category_id === category.id) == null;
});
filteredCategory.forEach(sc => {
options.push({id: sc.id, text: sc.name});
});
return options;
}
},
}
</script>
Replace:
let filteredCategory = this.categories.filter(category => {
return this.categoryOptions.find(selected => categoryOptions.category_id === category.id) == null;
});
per
let filteredCategory = this.categories.filter(category => {
return this.categoryOptions.find(selected => selected.category_id === category.id) == null;
});
note that you just forgot to replace categoryOptions with selected. But to ensure that the component is loaded, I advise you to make the props categories required, and ensure that it is persisted for the component before rendering.
<script>
export default {
props: {
'product',
'categories': {
type: [Array, Object],
required: true,
},
},
...
}
</script>
Another tip if you use the chrome browser, is to use a very cool extension which is Vue.js devtools to follow the status of your application.

Search functionality with rest api prevent DDOSing the server

The Problem
I have a search component and component which implements the search component. When I type something in the search bar after 1/2 second of not typing (debounce) the server should be hit and the results should be returned.
The solution i am trying to implement comes from this post on Stackoverflow
The code
This leads me to the following code.
I have search.vue
<template>
<label for="search">
<input
id="search"
class="w-full py-2 px-1 border-gray-900 border"
type="text"
name=":searchTitle"
v-model="searchFilter"
:placeholder="searchPlaceholder"
autocomplete="off"
v-on:keydown="filteredDataset"
/>
</label>
</template>
<script>
import {debounce} from 'lodash';
export default {
props: {
searchPlaceholder: {
type: String,
required: false,
default: ''
},
searchName: {
type: String,
required: false,
default: 'search'
}
},
data() {
return {
searchFilter: '',
}
},
methods: {
filteredDataset() {
console.log('event fired');
this.$emit('searchValue', this.searchFilter);
}
},
}
</script>
And product.vue
<template>
<div>
<div class="my-4">
<search
search-placeholder=""
search-name=""
v-on:searchValue="filterValue = $event"
v-model="productsFiltered"
>
</search>
<div class="flex w-full py-1 border px-2 my-2" v-for="product in productsFiltered"> (...)
</div>
</div>
</div>
</div>
</template>
<script>
import {debounce} from 'lodash';
export default {
data() {
return {
products: [],
filterValue: '',
filteredProducts: ''
}
},
computed: {
productsFiltered: {
get(){
console.log('getter called');
return this.filteredProducts;
},
set: _.debounce(function(){
console.log('setter called');
if (this.filterValue.length < 1) {
this.filteredProducts = [];
}
axios.get(`${apiUrl}search/` + this.filterValue)
.then(response => {
this.products = response.data.products;
const filtered = [];
const regOption = new RegExp(this.filterValue, 'ig');
for (const product of this.products) {
if (this.filterValue.length < 1 || product.productname.match(regOption)) {
filtered.push(product);
}
}
this.filteredProducts = filtered;
});
}, 500)
}
},
}
</script>
The result
The result is that the setter in the computed property in product.vue does not get called and no data is fetched from the server. Any ideas on how to solve this?
Your first code block imports debounce but does not use it. It also declares a prop, searchName, that isn't used. These aren't central issues, but clutter makes it harder to figure out what's going on.
Your second code block uses v-model but does not follow the required conventions for getting v-model to work with components:
the component must take a prop named value
the component must emit input events to signal changes to value
You have the component emit searchValue events, and handle them with a v-on that sets a data item. You seem to expect the v-model to call the setter, but as I noted, you haven't hooked it up to do so.
From what's here, you don't even really need to store the input value. You just want to emit it when it changes. Here's a demo:
const searchComponent = {
template: '#search-template',
props: {
searchPlaceholder: {
type: String,
required: false,
default: ''
}
},
methods: {
filteredDataset(searchFilter) {
console.log('event fired');
this.$emit('input', searchFilter);
}
}
};
new Vue({
el: '#app',
data() {
return {
products: [],
filterValue: '',
filteredProducts: ''
}
},
components: {
searchComponent
},
computed: {
productsFiltered: {
get() {
console.log('getter called');
return this.filteredProducts;
},
set: _.debounce(function() {
console.log('setter called');
if (this.filterValue.length < 1) {
this.filteredProducts = [];
}
setTimeout(() => {
console.log("This is the axios call");
this.filteredProducts = ['one','two','three'];
}, 200);
}, 500)
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<template id="search-template">
<label for="search">
<input
id="search"
class="w-full py-2 px-1 border-gray-900 border"
type="text"
name=":searchTitle"
:placeholder="searchPlaceholder"
autocomplete="off"
#input="filteredDataset"
/>
</label>
</template>
<div id="app">
<div class="my-4">
<search-component search-placeholder="enter something" v-model="productsFiltered">
</search-component>
<div class="flex w-full py-1 border px-2 my-2" v-for="product in productsFiltered"> (...)
</div>
</div>
</div>

vue-multiselect with both tagging and async, stops working

I'm using vue-multiselect and I would like the user to be able to search tags in the database using async and if they don't find what they want, enter their own tag. This means I'm using tagging and async. It works as expected until I add a tag not found in the , then the aysnc no longer searches. If I remove the added tag, then it does the async search again..
<template>
<div>
<label class="typo__label" for="ajax">Async multiselect</label>
<multiselect v-model="selectedTags" id="tags" label="name" track-by="code" placeholder="Type to search" open-direction="bottom" :options="tags" :taggable="true" #tag="addTag" :multiple="true" :searchable="true" :loading="isLoading" :internal-search="false" :clear-on-select="true" :close-on-select="false" :options-limit="300" :limit="3" :limit-text="limitText" :max-height="600" :show-no-results="false" :hide-selected="true" #search-change="asyncFind">
<template slot="clear" slot-scope="props">
<div class="multiselect__clear" v-if="selectedTags.length" #mousedown.prevent.stop="clearAll(props.search)"></div>
</template><span slot="noResult">Oops! No elements found. Consider changing the search query.</span>
</multiselect>
<pre class="language-json"><code>{{ selectedTags }}</code></pre>
</div>
</template>
<script>
import axios from 'axios';
import Multiselect from 'vue-multiselect'
export default {
components: {
Multiselect
},
props: {
userId: {
type: Number,
required: true
},
tagGroup: {
type: String,
required: true
}
},
data () {
return {
selectedTags: [],
tags: [],
isLoading: false
}
},
methods: {
addTag (newTag) {
const tag = {
name: newTag
}
this.tags.push(tag)
this.selectedTags.push(tag)
},
limitText (count) {
return `and ${count} other tags`
},
asyncFind (query) {
if( query.length > 3 ) {
this.isLoading = true
axios.get('/api/tags/'+this.tagGroup+'/'+query).then(response => {
this.tags = response.data.results.map(a => {
return { name: a.name.en };
});
})
}
},
clearAll () {
this.selectedTags = []
}
}
}
</script>
I'm using the component twice within another component:
<user-tags v-bind:tagGroup="'industry'" :typeahead-activation-threshold="2" :userId="user.id" :isSearchable="true"></user-tags>
<user-tags v-bind:tagGroup="'expertise'" :typeahead-activation-threshold="2" :userId="user.id" :isSearchable="true"></user-tags>

Resources