React how to update json on right position? - json

I'm using react to display a table with data and inputs with the values.
My question is how to add a event handler inside my code?
And also how to update my JSON on the right position with the right value?
My JSON is a state named cards.
My JSON looks like this:
cards: [{
index: 0,
export: true,
fieldname: 'sub_nr',
caption: "Subnr",
tags: []
}, {
index: 1,
export: false,
fieldname: 'address1',
caption: "Adress",
tags: []
}, {
index: 2,
export: false,
fieldname: 'post_code',
caption: "zipcode",
tags: []
}]
My code generates this:
This is my react code:
const Card = props => {
return props.connectDropTarget(
<tbody>
{ props.connectDragSource(
<tr className="pt-1 pb-1">
<td style={{textAlign: "center"}}>
<input type="checkbox" checked={props.export} />
</td>
<td>
<input type="text" value={props.caption} />
</td>
<td>
<input type="text" value={props.fieldname} />
</td>
<td>
<input type="option" />
</td>
<td style={{textAlign: "center"}}>
<i class="fa fa-arrows" aria-hidden="true"></i>
</td>
</tr>
) }
</tbody>
);
}
const typeCard = Symbol.for('##Type::Card');
const specTarget = {
drop(props) {
return {
index: props.index,
indexnr: props.indexnr,
};
}
};
const specSource = {
beginDrag(props) {
return {
index: props.index,
};
},
endDrag(props, monitor) {
if (!monitor.didDrop()) {
return;
}
const source = monitor.getItem();
const target = monitor.getDropResult();
if (source.index === target.index) {
return;
}
props.moveCard(source.index, target.indexnr);
}
};
const collectTarget = connect => ({
connectDropTarget: connect.dropTarget(),
});
const collectSource = (connect, monitor) => ({
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
});
const CardWithDnD = ReactDnD.DropTarget(typeCard, specTarget, collectTarget)(
ReactDnD.DragSource(typeCard, specSource, collectSource)(Card)
);
class App extends React.Component {
constructor(props) {
super(props);
this.moveCard = this.moveCard.bind(this);
this.state = {
cards: [{
index: 0,
export: true,
fieldname: 'sub_nr',
caption: "Subnr",
tags: []
}, {
index: 1,
export: false,
fieldname: 'address1',
caption: "Adress",
tags: []
}, {
index: 2,
export: false,
fieldname: 'post_code',
caption: "Postcode",
tags: []
}
]
};
}
moveCard (index, indexnr) {
const { cards } = this.state;
const sourceCard = cards.find(card => card.index === index);
const sortCards = cards.filter(card => card.index !== index)
sortCards.splice(indexnr, 0, sourceCard);
Object.keys(sortCards).forEach(function(nr) {
sortCards[nr].index = parseInt(nr, 10);
});
this.setState({ cards: sortCards });
console.log(this.state.cards);
}
render() {
const { cards } = this.state;
return (
<table className="offset-2 col-8">
{ cards.map((card, i) => (
<CardWithDnD
key={card.index}
indexnr={i}
moveCard={this.moveCard}
{...card}
/>
)) }
</table>
);
}
}
ReactDOM.render(
<ReactDnD.DragDropContextProvider backend={ReactDnDHTML5Backend}>
<App />
</ReactDnD.DragDropContextProvider>,
document.getElementById('root'),
);
I'm already able to update the index on dragging the table row, but I also want to update the Json fieldname and caption when I change the input.
How to do that?

Create a handleChange(event) function and assign it to get the input fields you want to get or set the input from. Then use the passed value inside (event.target.value) and update your state using this.setState() method inside the handleChange(event) function
Check this link for clarification

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>

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

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>

Checkbox is NOT check on click (React)

I created a todo list by using react. I get some problem that I want to create checkbox but my checkbox it does not work and I cannot solve :( I don't know what's wrong with that.
I set the data for each task and then I need to change the completed of some task, but it cannot click and change the completed task
This is my code
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todoData,
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if(todo.id === id) {
todo.completed = !todo.completed
// console.log(todo.completed)
}
return todo
})
return {
todos: updatedTodos
}
})
}
render() {
const todoItem = this.state.todos.map(item => <TodoItem key={item.id} item={item}
handleChange={this.handleChange}/>)
return (
<div>
<h1 className="header">My Todo Lists</h1>
{todoItem}
</div>
)
}
}
function TodoItem(props) {
let textItem = props.item.completed === true ?
<del>{props.item.text}</del> : props.item.text
return (
<div className="list">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p className="item">{textItem}</p>
</div>
)
}
And this is my data
const todoData = [
{
id: 1,
text: "Practice coding",
completed: false
},
{
id: 2,
text: "Grocery shopping",
completed: true
},
{
id: 3,
text: "Wash the dishes",
completed: true
},
{
id: 4,
text: "Take out the trash",
completed: false
},
{
id: 5,
text: "Teach my brother homework",
completed: false
}
]
Thank you for helping :)
Looks like on your handleChange you are mutating the existing state on your map transformation. you must return a new state instead.
Replace your handleChange with the following code:
handleChange(id) {
this.setState((prevState) => {
const updatedTodos = prevState.todos.map((todo) => {
return {
...todo,
completed: todo.id === id ? !todo.completed : todo.completed
};
});
return {
todos: updatedTodos
};
});
}

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