Nuxt + Algolia - Google Maps not updating the new results from instantsearch - google-maps

I am developing an instantsearch using nuxt. Googlemaps receives the first set on results on page load and renders the markers without any issues - however when searching the new results do not update on the map? Is there a way i can push the new set of results through to the map to render the markers?
I assume this will be the beforeMount hook and pass the results to updateMap?
pages/index.vue
<template>
<div class="root">
<div class="container">
<ais-instant-search-ssr
:search-client="searchClient"
index-name="dive_buddy"
>
<ais-configure :hits-per-page.camel="7">
<ais-search-box placeholder="Search here…" class="searchbox" />
<ais-stats />
<div class="h-screen">
<ais-hits>
<template #default="{ items }">
<div class="grid grid-cols-2 gap-2">
<div v-if="items.length">
<div
v-for="item in items"
:key="item.objectID"
class="my-2 p-2 border"
>
<SiteRow
:site="item"
#mouseover.native="highlightMarker(item.objectID, true)"
#mouseout.native="highlightMarker(item.objectID, false)"
/>
</div>
</div>
<div v-else>No results found</div>
<Map
:items="items"
:lat="items[0].lat"
:lng="items[0].lng"
/>
</div>
</template>
</ais-hits>
<ais-pagination />
</div>
</ais-configure>
</ais-instant-search-ssr>
</div>
</div>
</template>
<script>
import {
AisInstantSearchSsr,
AisConfigure,
AisHits,
AisSearchBox,
AisStats,
AisPagination,
createServerRootMixin,
} from 'vue-instantsearch'
import algoliasearch from 'algoliasearch/lite'
import _renderToString from 'vue-server-renderer/basic'
function renderToString(app) {
return new Promise((resolve, reject) => {
_renderToString(app, (err, res) => {
if (err) reject(err)
resolve(res)
})
})
}
const searchClient = algoliasearch(
'xxx', // AppID
'xxxxxxxx' // APIKey
)
export default {
components: {
AisInstantSearchSsr,
AisConfigure,
AisHits,
AisSearchBox,
AisStats,
AisPagination,
},
mixins: [
createServerRootMixin({
searchClient,
indexName: 'dive_buddy',
}),
],
data() {
return {
searchClient,
}
},
head() {
return {
link: [
{
rel: 'stylesheet',
href: 'https://cdn.jsdelivr.net/npm/instantsearch.css#7.4.5/themes/satellite-min.css',
},
],
}
},
serverPrefetch() {
return this.instantsearch
.findResultsState({
component: this,
renderToString,
})
.then((algoliaState) => {
this.$ssrContext.nuxt.algoliaState = algoliaState
})
},
beforeMount() {
const results =
(this.$nuxt.context && this.$nuxt.context.nuxtState.algoliaState) ||
window.__NUXT__.algoliaState
this.instantsearch.hydrate(results)
// Remove the SSR state so it can't be applied again by mistake
delete this.$nuxt.context.nuxtState.algoliaState
delete window.__NUXT__.algoliaState
},
methods: {
highlightMarker(id, isHighlighted) {
document
.getElementsByClassName(`id-${id}`)[0]
?.classList?.toggle('marker-highlight', isHighlighted)
},
},
}
</script>
components/Map.vue
<template>
<div ref="map" class="h-screen"></div>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
lat: {
type: Number,
required: true,
},
lng: {
type: Number,
required: true,
},
},
mounted() {
this.updateMap()
},
methods: {
// run showMap function in maps.client.js
async updateMap() {
await this.$maps.showMap(
this.$refs.map,
this.lat,
this.lng,
this.getMarkers()
)
},
// pass through markers to googlemaps
getMarkers() {
return this.items.map((item) => {
return {
...item,
}
})
},
},
}
</script>
plugins/maps.client.js
export default function(context, inject){
let isLoaded = false
let waiting = []
addScript()
inject('maps', {
showMap,
})
function addScript(){
const script = document.createElement('script')
script.src = "https://maps.googleapis.com/maps/api/js?key=xxxx&libraries=places&callback=initGoogleMaps"
script.async = true
window.initGoogleMaps = initGoogleMaps
document.head.appendChild(script)
}
function initGoogleMaps(){
isLoaded = true
waiting.forEach((item) => {
if(typeof item.fn === 'function'){
item.fn(...item.arguments)
}
})
waiting = []
}
function showMap(canvas, lat, lng, markers){
if(!isLoaded){
waiting.push({
fn: showMap,
arguments,
})
return
}
const mapOptions = {
zoom: 18,
center: new window.google.maps.LatLng(lat, lng),
disableDefaultUI: true,
zoomControl: true,
styles:[{
featureType: 'poi.business',
elementType: 'labels.icon',
stylers:[{ visibility: 'off' }]
}]
}
const map = new window.google.maps.Map(canvas, mapOptions)
if(!markers){
const position = new window.google.maps.LatLng(lat, lng)
const marker = new window.google.maps.Marker({
position,
clickable: false,
})
marker.setMap(map)
return
}
const bounds = new window.google.maps.LatLngBounds()
let index = 1
markers.forEach((item) => {
const position = new window.google.maps.LatLng(item.lat, item.lng)
const marker = new window.google.maps.Marker({
position,
label: {
text: `${index}`,
color: 'black',
className: `marker id-${item.objectID}`
},
icon: 'https://maps.gstatic.com/mapfiles/transparent.png',
clickable: false,
})
marker.setMap(map)
bounds.extend(position)
index++
})
map.fitBounds(bounds)
}
}
EDIT
As per #Bryan's suggestion - I have previously tried to follow algolia's documentation using vue-googlemaps plugin but this does not work on Nuxt as the map does not load as well as these errors -
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'g')
Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'h')

Although this isnt a solution to the above - I have found a workaround using a different library: vue2-google-maps as this works with Nuxt (see docs).
pages/index.vue
<template>
<div class="root">
<div class="container">
<ais-instant-search-ssr
:search-client="searchClient"
index-name="dive_centres"
>
<ais-configure :hits-per-page.camel="7">
<ais-search-box placeholder="Search here…" class="searchbox" />
<ais-stats />
<div class="h-screen">
<ais-hits>
<template #default="{ items }">
<div class="grid grid-cols-2 gap-2">
<div v-if="items.length">
<div
v-for="item in items"
:key="item.objectID"
class="my-2 p-2 border"
>
<SiteRow
:site="item"
#mouseover.native="highlightMarker(item.objectID, true)"
#mouseout.native="highlightMarker(item.objectID, false)"
/>
</div>
</div>
<div v-else>No results found</div>
<Map />
</div>
</template>
</ais-hits>
<ais-pagination />
</div>
</ais-configure>
</ais-instant-search-ssr>
</div>
</div>
</template>
<script>
import {
AisInstantSearchSsr,
AisConfigure,
AisHits,
AisSearchBox,
AisStats,
AisPagination,
createServerRootMixin,
} from 'vue-instantsearch'
import algoliasearch from 'algoliasearch/lite'
import _renderToString from 'vue-server-renderer/basic'
function renderToString(app) {
return new Promise((resolve, reject) => {
_renderToString(app, (err, res) => {
if (err) reject(err)
resolve(res)
})
})
}
const searchClient = algoliasearch(
'xxx', // AppID
'xxxxxxxx' // APIKey
)
export default {
components: {
AisInstantSearchSsr,
AisConfigure,
AisHits,
AisSearchBox,
AisStats,
AisPagination,
},
mixins: [
createServerRootMixin({
searchClient,
indexName: 'dive_sites',
}),
],
head() {
return {
link: [
{
rel: 'stylesheet',
href: 'https://cdn.jsdelivr.net/npm/instantsearch.css#7.4.5/themes/satellite-min.css',
},
],
}
},
serverPrefetch() {
return this.instantsearch
.findResultsState({
component: this,
renderToString,
})
.then((algoliaState) => {
this.$ssrContext.nuxt.algoliaState = algoliaState
})
},
beforeMount() {
const results =
(this.$nuxt.context && this.$nuxt.context.nuxtState.algoliaState) ||
window.__NUXT__.algoliaState
this.instantsearch.hydrate(results)
// Remove the SSR state so it can't be applied again by mistake
delete this.$nuxt.context.nuxtState.algoliaState
delete window.__NUXT__.algoliaState
},
methods: {
highlightMarker(id, isHighlighted) {
console.log(id)
document
.getElementsByClassName(`id-${id}`)[0]
?.classList?.toggle('marker-highlight', isHighlighted)
},
},
}
</script>
components/Map.vue
<template>
<div v-if="state" class="ais-GeoSearch">
<GmapMap
:center="center"
:zoom="zoom"
map-type-id="terrain"
class="h-full w-full"
:options="options"
>
<GmapMarker
v-for="item in state.items"
:key="item.objectID"
:position="item._geoloc"
:clickable="false"
:draggable="true"
:icon="{ url: require('~/static/images/dive-site-small.svg') }"
/>
</GmapMap>
</div>
</template>
<script>
import { createWidgetMixin } from 'vue-instantsearch'
import { connectGeoSearch } from 'instantsearch.js/es/connectors'
export default {
mixins: [createWidgetMixin({ connector: connectGeoSearch })],
data() {
return {
zoom: 4,
options: {
disableDefaultUI: true,
},
}
},
computed: {
center() {
return this.state.items.length !== 0
? this.state.items[0]._geoloc
: { lat: 48.864716, lng: 2.349014 }
},
},
}
</script>

Related

react async select trouble. Select creation in lopp

I have some troubles with asyncselect. Idea is to make a loop with many select fields with names as html array. Like fields[index], field_condition[index]. So for now it loads data into first select, but it doesn't set a value, and it's not loading second select(it take field it and loads data based on that field id), I've checked and all requests seems to be good. Two questions: How to make html arrays using asyncselect and why it doesn't load into second select?
Request for first asyncselect:
loadFields(inputValue) {
const { cookies, getFields } = this.props;
return new Promise(resolve => {
getFields({
headers: {
token: cookies.get('token')
},
per_page: 20,
page: 1,
dispatchAction: false,
resolve: (response) => {
resolve(filter(inputValue, response.data.results.map((field) => ({
...field,
value: field.id,
label: field.name,
}))));
},
reject: (error) => {
resolve(error);
}
});
});
}
Here is my second request that take additional argument(not working at all):
loadFieldConditions(inputValue, filter_id) {
const { cookies, getFieldConditions } = this.props;
return new Promise(resolve => {
getFieldConditions({
headers: {
token: cookies.get('token')
},
field_id: filter_id,
per_page: 20,
page: 1,
dispatchAction: false,
resolve: (response) => {
resolve(filter(inputValue, response.data.results.map((condition) => ({
...condition,
value: condition.id,
label: condition.name,
}))));
},
reject: (error) => {
resolve(error);
}
});
});
}
filter.fields.map((item, index) => {
return <div>
<div className="filter-item">
{/* <span class="missive-column-auto text-e">•</span> */}
<div className="filter-field">
<AsyncSelect
value={ fields[index] }
// onMenuClose={ closeDropdown.bind(null, item.field.id) }
onChange={ onChange.bind(null, 'fields['+index+']') }
className="react-select-container"
ref={(node) => this.fields[index] = node }
loadOptions={ loadFields }
classNamePrefix="react-select"
placeholder="Select field"
blurInputOnSelect={ true }
defaultOptions
cacheOptions
isClearable
/>
</div>
<div className="filter-field">
<AsyncSelect
value={ item.field_condition.id }
// onMenuClose={ closeDropdown.bind(null, item.field.id) }
// onChange={ onChange.bind(null, 'fields', item.field.id) }
className="react-select-container"
// ref={(node) => item.field.id = node }
loadOptions={(inputValue) => { loadFieldConditions(inputValue, item.field.id) }}
classNamePrefix="react-select"
placeholder="Select condition"
blurInputOnSelect={ true }
defaultOptions
cacheOptions
isClearable
/>
</div>

google is not defined when using vue2-google-maps

I am using Nuxt and vue2-google-maps but I do have the following error
module error: google is not defined
I read the official GitHub FAQ, so I used this.$gmapApiPromiseLazy().then().
But, I do have the aforementioned error.
getCurrentPositionandBathroom method is to get current position and search for convenience store around current position.
<template>
<v-app>
<v-btn class="blue white--text" #click="getCurrentPositionandBathroom"> search for bathroom! </v-btn>
<GmapMap
ref="mapRef"
:center="maplocation"
:zoom="15"
map-type-id="roadmap"
style="height: 300px; width: 900px"
>
<GmapMarker
v-for="m in markers"
:key="m.id"
:position="m.position"
:clickable="true"
:draggable="true"
:icon="m.icon"
#click="center = m.position"
/>
</GmapMap>
</v-app>
</template>
<script>
export default {
data() {
return {
maplocation: { lat: 35.6814366, lng: 139.767157 },
markers: [],
}
},
methods: {
getCurrentPositionandBathroom() {
if (process.client) {
if (!navigator.geolocation) {
alert('Japanese sentences')
return
}
navigator.geolocation.getCurrentPosition(this.success, this.error)
}
},
success(position) {
this.maplocation.lat = position.coords.latitude
this.maplocation.lng = position.coords.longitude
this.$gmapApiPromiseLazy().then(() => {
google.maps.event.addListenerOnce(
this.$refs.mapRef.$mapObject,
'idle',
function () {
this.getBathroom()
}.bind(this),
)
})
},
getBathroom() {
const map = this.$refs.mapRef.$mapObject
const placeService = new google.maps.places.PlacesService(map)
placeService.nearbySearch(
{
location: new google.maps.LatLng(this.maplocation.lat, this.maplocation.lng),
radius: 500,
type: ['convenience_store'],
},
function (results, status) {
if (status === google.maps.places.PlacesServiceStatus.OK) {
results.forEach((place) => {
const icon = {
url: place.icon,
scaledSize: new google.maps.Size(30, 30),
}
const marker = {
position: place.geometry.location,
icon,
title: place.name,
id: place.place_id,
}
this.markers.push(marker)
})
}
}.bind(this),
)
},
error(errorMessage) {
switch (errorMessage.code) {
case 1:
alert('Japanese sentences')
break
case 2:
alert('Japanese sentences')
break
case 3:
alert('Japanese sentences')
break
default:
alert('Japanese sentences')
break
}
},
},
}
</script>
What I should I do?
PS: I can see the Google Maps. In other words, Google Maps is displayed.
Alright, so there was quite a few configuration to do but I achieved to have a working map. Your this.getBathroom() method was not working for me, but this is related to the API or how you handle the logic I guess.
I basically followed the package README and it all went smooth at the end. Nothing special and google is available as explained in the following section:
If you need to gain access to the google object
Here is the final code of the .vue file
<template>
<div>
<button class="blue white--text" #click="getCurrentPositionandBathroom">
search for bathroom!
</button>
<GmapMap
ref="mapRef"
:center="maplocation"
:zoom="15"
map-type-id="roadmap"
style="height: 300px; width: 900px"
>
<GmapMarker
v-for="m in markers"
:key="m.id"
:position="m.position"
:clickable="true"
:draggable="true"
:icon="m.icon"
#click="center = m.position"
/>
</GmapMap>
</div>
</template>
<script>
import { gmapApi } from 'vue2-google-maps'
export default {
data() {
return {
maplocation: { lat: 35.6814366, lng: 139.767157 },
markers: [],
}
},
computed: {
google: gmapApi,
},
methods: {
getCurrentPositionandBathroom() {
if (process.client) {
if (!navigator.geolocation) {
alert('Japanese sentences')
return
}
navigator.geolocation.getCurrentPosition(this.success, this.error)
}
},
success(position) {
this.maplocation.lat = position.coords.latitude
this.maplocation.lng = position.coords.longitude
// this.$gmapApiPromiseLazy().then(() => { // not needed here anymore
this.google.maps.event.addListenerOnce(
this.$refs.mapRef.$mapObject,
'idle',
function () {
this.getBathroom()
}.bind(this)
)
// })
},
getBathroom() {
const map = this.$refs.mapRef.$mapObject
const placeService = new this.google.maps.places.PlacesService(map)
placeService.nearbySearch(
{
location: new this.google.maps.LatLng(
this.maplocation.lat,
this.maplocation.lng
),
radius: 500,
type: ['convenience_store'],
},
function (results, status) {
if (status === this.google.maps.places.PlacesServiceStatus.OK) {
results.forEach((place) => {
const icon = {
url: place.icon,
scaledSize: new this.google.maps.Size(30, 30),
}
const marker = {
position: place.geometry.location,
icon,
title: place.name,
id: place.place_id,
}
this.markers.push(marker)
})
}
}.bind(this)
)
},
error(errorMessage) {
switch (errorMessage.code) {
case 1:
alert('Japanese sentences')
break
case 2:
alert('Japanese sentences')
break
case 3:
alert('Japanese sentences')
break
default:
alert('Japanese sentences')
break
}
},
},
}
</script>
You can find the useful commit on my github repo here.
This is how it looks at the end, no errors so far.
PS: I didn't saw that you were using Vuetify, so I didn't bother bringing it back later on.

how to create a custom autocomplete component in vue js?

Currently i am using buefy autocomplete.But there are a few issues with it.
DepartmentDetail.vue
<template slot-scope="props">
<div class="container is-fluid">
<b-loading :is-full-page="true" :active.sync="this.isLoading"></b-loading>
<b-field label="Business Unit">
<b-autocomplete
:data="dataBusinessUnit"
placeholder="select a business unit..."
field="businessUnit"
:loading="isFetching"
:value="this.objectData.businessUnit"
#typing="getAsyncDataBusinessUnit"
#select="(option) => {updateValue(option.id,'businessUnit')}"
>
<template slot-scope="props">
<div class="container">
<p>
<b>ID:</b>
{{props.option.id}}
</p>
<p>
<b>Description:</b>
{{props.option.description}}
</p>
</div>
</template>
<template slot="empty">No results found</template>
</b-autocomplete>
</b-field>
</div>
</template>
Function that fetches the results based on user input-
getAsyncDataBusinessUnit: debounce(function(name) {
// console.log('getAsyncDataBusinessUnit you typed'+name);
if (!name.length) {
this.dataBusinessUnit = [];
return;
}
this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/${name}`)
.then(response => {
this.dataBusinessUnit = [];
response.forEach(item => {
this.dataBusinessUnit.push(item);
});
})
.catch(error => {
this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
this.isFetching = false;
});
}, 500),
So instead of using the buefy's b-autocomplete i want to create and use my own autocomplete component.
So i went ahead and created my own autocomplete component and called it from the DepartmentDetail vue page like this-
DepartmentDetail.vue
<b-field label="Custom Business Unit ">
<AutoComplete :method="getAsyncDataBusinessUnit" title='businessUnit' :autocompleteData="dataBusinessUnit" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
AutoComplete.vue
<template>
<div class="autocomplete">
<input style="font-size: 12pt; height: 36px; width:1800px; " type="text" v-model="this.objectData[this.title]" #input="getAsyncDataBusinessUnit"/>
<ul v-show="isFetching" >
<li v-for="(dataBusinessUnit, i) in dataBusinessUnit" :key="i" #click="setResult(dataBusinessUnit)" >
<!-- {{ autocompleteData }} -->
<template v-if="title!='manager'">
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>Description:</b>
{{dataBusinessUnit.description}}
</p>
</div>
</template>
<template v-else>
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>First Name:</b>
{{dataBusinessUnit.firstName}}
</p>
<p>
<b>Last Name:</b>
{{dataBusinessUnit.lastName}}
</p>
</div>
</template>
</li>
</ul>
</div>
</template>
<script>
import { viewMixin } from "../viewMixin.js";
import schemaData from '../store/schema';
import debounce from "lodash/debounce";
import api from "../store/api";
const ViewName = "AutoComplete";
var passedview;
export default {
name: "AutoComplete",
props: {
method: {
type: Function
},
title: String,
viewname:String,
autocompleteData: {
type: Array,
required: true
}
},
data() {
return {
// results: [],
dataBusinessUnit: [],
isFetching: false
// vignesh: this.objectData[this.title]
};
},
computed: {
viewData() {
return this.$store.getters.getViewData('DepartmentDetail')
},
objectData() {
return this.$store.getters.getApiData(this.viewData.api_id).data
},
sessionData() {
return this.$store.getters.getSessionData()
},
isLoading() {
return this.$store.getters.getApiData(this.viewData.api_id).isLoading
},
newRecord() {
return this.$route.params.id === null;
},
getTitle() {
return this.title
}
},
mounted() {
},
methods: {
setResult(result) {
this.updateValue(result.id,this.title);
// localStorage.setItem(this.title,result.id );
this.isFetching = false;
},
updateValue(newValue, fieldName) {
var val;
var schema = schemaData[this.viewData.schema];
if(typeof schema!=='undefined' && schema['properties'][fieldName]['type'] == 'date'){
val = this.formatDate(newValue);
} else {
val = newValue;
}
this.$store.dispatch('updateDataObjectField', {
key: this.viewData.api_id,
field: fieldName,
value: val
});
},
getAsyncDataBusinessUnit: debounce(function(name) {
console.log('getAsyncDataBusinessUnit you typed'+name.target.value);
if (!name.target.value.length) {
this.dataBusinessUnit = [];
this.isFetching = false;
return;
}
// this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/${name.target.value}`)
.then(response => {
this.dataBusinessUnit = [];
if (!response.length)
{
console.log('inside if')
this.isFetching = false;
}
else{
console.log('inside else')
response.forEach(item => {
this.dataBusinessUnit.push(item);
});
this.isFetching = true;
}
console.log('length of dataBusinessUnit is '+(this.dataBusinessUnit).length)
console.log('contents of dataBusinessUnit array '+JSON.stringify(this.dataBusinessUnit))
})
.catch(error => {
this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
// this.isFetching = true;
});
}, 500),
},
components: {
}
};
</script>
The issue iam facing is whenever i start to type anything into the Custom Business Unit input field, then immediately after 1-2 seconds the value gets reset(with the value coming from the store getters ).This however does not happen if i remove the line this.dataBusinessUnit = []; in the getAsyncDataBusinessUnit function . Why is this happening? I even tried using :input instead of v-model for the input field , but i am still facing the same issue.And also second issue is when i click an existing record under DepartmentDetail page, the value that should be set for the custom business unit input field coming from the store getters (this.objectData.businessUnit) is not showing up sometimes? please help
The basic logic of an autocomplete is not really hard:
Vue.component('Autocomplete', {
props: ['list'],
data() {
return {
input: null
}
},
template: `<div><input v-model="input" #input="handleInput"><div class="bordered" v-if="input"><ul><li v-for="(item, i) in list" :key="i" #click="setInput(item)">{{ item }}</li></ul></div></div>`,
methods: {
handleInput(e) {
this.$emit('input', e.target.value)
},
setInput(value) {
this.input = value
this.$emit('input', value)
}
}
})
new Vue({
el: "#app",
computed: {
filteredList() {
if (this.filterInput) {
return this.list.filter(e => e.toLowerCase().indexOf(this.filterInput.toLowerCase()) !== -1)
} else {
return this.list
}
}
},
data: {
filterInput: null,
list: [
"First",
"Second",
"Third",
"Fourth",
"Fifth",
"Sixth",
"Seventh"
]
},
methods: {
handleInput(e) {
this.filterInput = e
}
}
})
.bordered {
border: 1px solid black;
display: block;
}
ul li {
cursor: pointer;
}
ul li:hover {
background: rgba(0, 0, 0, 0.3)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<autocomplete :list="filteredList" #input="handleInput" />
</div>
I wouldn't handle the data in the presentational component (autocomplete is a presentational component in the structure I created), but in its parent-container. That way the autocomplete just gets a list as a prop and displays it; every action is $emitted to the parent, who does the data-handling.
It's easier to control displayed data this way - even with an async data source.

How to close autocomplete dropdown when clicked outside?

How to close autocomplete dropdown when clicked on any outside area? Currently i am calling my custom autocomplete component(child presentation component) 3 times with different labels from another vue page(parent) called departmentDetail.vue.
Example:
departmentDetail.vue
<b-field label="Custom Business Unit ">
<AutoComplete :method="getAsyncDataBusinessUnit" title='businessUnit' :autocompleteData="dataBusinessUnit" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
<b-field label="Custom Managers">
<AutoComplete :method="getAsyncData" title='manager' :autocompleteData="dataManager" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
<b-field label="Custom Location">
<AutoComplete :method=" getAsyncDataLocation" title='location' :autocompleteData="dataLocation" viewname='DepartmentDetail'>
</AutoComplete>
</b-field>
AutoComplete.vue (Custom component created by me)
<template>
<div class="autocomplete">
<input style="font-size: 12pt; height: 36px; width:1800px; " type="text" v-model="objectData[title]" #focus="getAsyncDataBusinessUnit" #input="getAsyncDataBusinessUnit"/>
<ul v-show="isFetching" >
<li v-for="(dataBusinessUnit, i) in dataBusinessUnit" :key="i" #click="setResult(dataBusinessUnit)" >
<template v-if="title!='manager'">
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>Description:</b>
{{dataBusinessUnit.description}}
</p>
</div>
</template>
<template v-else>
<div class="container">
<p>
<b>ID:</b>
{{dataBusinessUnit.id}}
</p>
<p>
<b>First Name:</b>
{{dataBusinessUnit.firstName}}
</p>
<p>
<b>Last Name:</b>
{{dataBusinessUnit.lastName}}
</p>
</div>
</template>
</li>
</ul>
</div>
</template>
<script>
import { viewMixin } from "../viewMixin.js";
import schemaData from '../store/schema';
import debounce from "lodash/debounce";
import api from "../store/api";
const ViewName = "AutoComplete";
var passedview;
export default {
name: "AutoComplete",
props: {
method: {
type: Function
},
title: String,
viewname:String,
autocompleteData: {
type: Array,
required: true
}
},
data() {
return {
// results: [],
dataBusinessUnit: [],
results: [],
isFetching: false
// vignesh: this.objectData[this.title]
};
},
computed: {
viewData() {
return this.$store.getters.getViewData('DepartmentDetail')
},
objectData() {
return this.$store.getters.getApiData(this.viewData.api_id).data
},
sessionData() {
return this.$store.getters.getSessionData()
},
isLoading() {
return this.$store.getters.getApiData(this.viewData.api_id).isLoading
},
newRecord() {
return this.$route.params.id === null;
},
getTitle() {
return this.title
}
},
mounted() {
},
methods: {
setResult(result) {
this.updateValue(result.id,this.title);
// localStorage.setItem(this.title,result.id );
this.isFetching = false;
},
updateValue(newValue, fieldName) {
var val;
var schema = schemaData[this.viewData.schema];
if(typeof schema!=='undefined' && schema['properties'][fieldName]['type'] == 'date'){
val = this.formatDate(newValue);
} else {
val = newValue;
}
this.$store.dispatch('updateDataObjectField', {
key: this.viewData.api_id,
field: fieldName,
value: val
});
},
getAsyncDataBusinessUnit: debounce(function(name) {
console.log('getAsyncDataBusinessUnit you typed'+name.target.value);
if (!name.target.value.length) {
// this.results = [];
// this.dataBusinessUnit = [...this.results];
// this.isFetching = false;
// return;
}
// this.isFetching = true;
api
.getSearchData(this.sessionData.key,`/businessunit/`,{ filter: `{id}like'%${name.target.value}%'` })
.then(response => {
this.results = [];
if (!response.length)
{
console.log('inside if ')
this.isFetching = false;
}
else{
console.log('inside else')
response.forEach(item => {
this.results.push(item);
});
// this.dataBusinessUnit=this.results
this.dataBusinessUnit = [...this.results];
this.isFetching = true;
}
console.log('length of dataBusinessUnit is '+(this.dataBusinessUnit).length)
console.log('contents of dataBusinessUnit array '+JSON.stringify(this.dataBusinessUnit))
})
.catch(error => {
//this.dataBusinessUnit = [];
throw error;
})
.finally(() => {
// this.isFetching = true;
});
}, 500),
},
components: {
}
};
</script>
Screenshot of the Department screen
And why is it that when the page loads sometimes the values dont show up in these input fields? But upon focus or if i type anything then sudddenly the value shows up?Any idea why this is happening?
About your last question:
"And why is it that when the page loads sometimes the values dont show up in these input fields? But upon focus or if i type anything then sudddenly the value shows up?Any idea why this is happening?"
It looks like you are usesing API requests on computed props.
Computed props are pre renderd values. If your API works async then the computed is renderd "empty" before the full request is resolved.
you could try data props and set them with the API setter in Mounted() or Created().
EDIT:
It could look something like this:
data() {
return {
// results: [],
dataBusinessUnit: [],
results: [],
viewData: [],
objectData:[],
sessionData:[],
isLoading: [],
isFetching: false
// vignesh: this.objectData[this.title]
};
},
computed: {
newRecord() {
return this.$route.params.id === null;
},
getTitle() {
return this.title
}
},
mounted() {
this.viewData = this.$store.getters.getViewData('DepartmentDetail');
this.objectData = this.$store.getters.getApiData(this.viewData.api_id).data;
this.sessionData = this.$store.getters.getSessionData();
this.isLoading = this.$store.getters.getApiData(this.viewData.api_id).isLoading;
},

How to access a JSON value with Vue.js without using v-for

I have the following json:
[{id: 3,
pais: "Chile",
fuso_atual: "-3",
fuso_api: "-7200",
dst: "1",
dst_start: "1476586800",
dst_end: "1487469599",
created_at: null,
updated_at: "2016-12-11 19:19:11"
}]
and I want to access this properties, but without using v-for or something like this, I want a simple access (in my html) like {{paises[0].pais}}, but when I try this, I get an error "Cannot read property 'pais' of undefined(…)"
My component:
var getTimeZone = {
template: '#time-zone-list-template',
data: function(){
return {
paises: []
}
},
created: function(){
this.getTimeZone2();
},
methods: {
getTimeZone2: function(){
this.$http.get('api/paises').then((response) => {
this.paises = response.body;
});
}
}
};
var app = new Vue({
el: '#app',
components: {
'time-zone-list': getTimeZone
}
});
My html:
<div id="app">
<time-zone-list></time-zone-list>
</div>
<template id="time-zone-list-template">
<div class="time-and-date-wrap">
{{ paises[0].pais }}
</div>
</template>
Any idea?
edit: I can access {{ paises[0] }} with success, but not .pais
edit2: If I use v-for I can navigate in the inside properties
Thanks
I found another solution =>>> v-if="paises.length" this solved my problem =]
var getTimeZone = {
template: '#time-zone-list-template',
data: function(){
return {
paises: []
}
},
created: function(){
this.getTimeZone();
},
methods: {
getTimeZone: function(){
this.$http.get('api/paises').then((response) => {
this.paises = response.body;
});
}
}
};
Then in my template
<template id="time-zone-list-template">
<div class="time-and-date-wrap" v-if="paises.length">
{{ paises[0].pais }}
</div>
</template>
Jeff Mercado's answer in the comment correctly describes why this fails.
You could add a computed property like this...
var getTimeZone = {
template: '#time-zone-list-template',
data: function(){
return {
paises: []
}
},
created: function(){
this.getTimeZone2();
},
computed: {
elPrimerPais: function() {
return this.paises.length > 0 ? this.paises[0].pais : '';
}
},
methods: {
getTimeZone2: function(){
this.$http.get('api/paises').then((response) => {
this.paises = response.body;
});
}
}
};
Then in your template you can do this..
<template id="time-zone-list-template">
<div class="time-and-date-wrap">
{{ elPrimerPais }}
</div>
</template>