Dynamic dropdown options with Vuetify and Firestore - javascript

I'm using nuxt, vuetify and firebase (cloud firestore for database) to build a contractor's database app. There will be a multiple of scope of works (SOW) & specializations for contractor to choose from when they filling a form.
For 1 scope of work, there will be multiple choices of specialization. So, whenever the user choose a SOW on the first select option, there will be just a limited number of specialization to choose from on the second select option that related to that particular SOW. After picking the specialization, the 3rd select option will auto display the code assigned to that specialization. All the data for SOWs & specializations is get from firestore database.
I have no problem to load the data from firestore for all 3 options. It just that it not do the dynamic dropdown the way I wanted.
I already referred to https://jsfiddle.net/mani04/Lgxrcc5p/ but cant really working out the solution. Is there an a way to do this?
Image of the 3 select options here : After select scope, nothing happen on the 2nd select
Thanks!
Here is my template code using vuetify v-select
<v-col cols="12" sm="6" md="5">
<v-select
:items="scope"
item-text="scope"
v-model="editedItem.scope"
label="Scope"
#click="listofscope"
></v-select>
</v-col>
<v-col cols="12" sm="6" md="5">
<v-select
:items="special"
item-text="specialization"
v-model="editedItem.specialization"
:disabled="listofspecialization.length == 0"
label="Specialization"
#click="listofspecialization"
></v-select>
</v-col>
<v-col cols="12" sm="6" md="5">
<v-select
:items="special"
item-text="code"
v-model="editedItem.code"
:disabled="listofspecialization.length == 0"
label="Code"
#click="listofspecialization"
></v-select>
</v-col>
Here is my script part
data: () => ({
// ..my other codes
scope: [],
editedIndex: -1,
editedItem: {
scope: '',
},
defaultItem: {
scope: '',
},
special: [],
editedIndex: -1,
editedItem: {
specialization: '',
code: '',
},
defaultItem: {
specialization: '',
code: '',
},
)}
listofscope() {
this.scope = []
db
.collection('Scope of Works')
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
this.scope.push({ ...doc.data(), id: doc.id })
})
if (this.listofscope.length > 0) {
this.listofspecialization = [this.scope]
}
console.log(this.scope)
})
},
listofspecialization() {
this.special = []
db
.collectionGroup("Specialization")
.get()
.then((querySnapshot) => {
querySnapshot.forEach((doc) => {
// doc.data() is never undefined for query doc snapshots
this.special.push({ ...doc.data(), id: doc.id })
})
if (this.listofspecialization.length > 0) {
this.code = [this.scope][this.specialization]
}
console.log(this.special)
})

I put my answer here in case if someone had the same problem with me. Yes, using watchers solved the problem.
Now, whenever I pick a scope, a limited choice of specialization will list out. Then, when I picked a specialization, it will automatically display the code assigned to that particular specialization.
Here's how I do it:
My HTML part:
<v-col cols="12" sm="6" md="5">
<v-select
:items="scopes"
item-text="scope"
v-model="editedItem.scope"
label="Scope"
></v-select>
</v-col>
<v-col cols="12" sm="6" md="5">
<v-select
:items="specializationFiltered"
item-text="specialization"
v-model="editedItem.specialization"
label="Specialization"
return-object
:disabled="specializations.length == 0"
></v-select>
</v-col>
<v-col cols="12" sm="6" md="5">
<v-select
:items="codes"
item-text="code"
v-model="editedItem.specialization.code"
label="Code"
:disabled="true"
></v-select>
</v-col>
My script part
watch: {
"editedItem.scope"(newVal) {
this.specializationFiltered = [];
if (this.editedItem.scope) {
firestore
.collection("Scope of Works")
.doc(this.editedItem.scope)
.collection("Specialization")
.get()
.then(querySnapshot => {
console.log(querySnapshot);
querySnapshot.forEach(doc => {
console.log(doc.data());
this.specializationFiltered.push(doc.data());
});
});
}
}
},
Thanks :))

Related

how to display every value of an nested array

In my vue application I have a nested array where the user can select one date and multiple times and this is saved as one object. What I want to achieve now is to display the date as a header (Works) and under it display all times belonging to that specific date. Right now I could only make it work with one time. But if one object has multiple times I still could only make it work to show one time.
Could someone take a look at my code?
code to display the insides of the array:
<v-card-text >
<v-row>
<v-col cols="4" v-for="(i, index) in datesFinal" >
<h4>{{i.date}}</h4>
<v-chip>{{i.times[0].startTime + ":" + i.times[0].endTime}}</v-chip>
</v-col>
</v-row>
</v-card-text>
Script tag:
<script>
import MeetingsTableComponent from "#/components/MeetingsTableComponent";
import DatePickerComponent from "#/components/DatePickerComponent";
export default {
name: "NextMeetingCardComponent",
data: () => ({
selectedTime: [],
dates: new Date().toISOString().substr(0,10),
datesFinal: [],
dialog: false,
menu: false,
modal: false,
menu2: false,
menu3: false
}),
methods:{
addTimeFields(){
this.selectedTime.push({
startTime:"",
endTime: "",
})
},
saveDateAndTIme(e) {
this.datesFinal.push({
date: this.dates,
times: this.selectedTime
}
)
this.selectedTime = [];
}
My method does not work because I am telling it to access [0] but this is just my guess
You could try this solution::
<v-card-text>
<v-row>
<v-col cols="4" v-for="(item, index) in datesFinal" :key="index" >
<h4>{{item.date}}</h4>
<v-chip-group v-if="item.times.length !== 0">
<v-chip v-for="(time, i) in item.times" :key="i">
{{time.startTime + ":" + time.endTime}}
</v-chip>
</v-chip-group>
<v-chip-group v-else>
<v-chip>no times </v-chip>
</v-chip-group>
</v-col>
</v-row>
</v-card-text>
Ummm, I think I can provide a thinking that maybe you can use Set to filter the same date. Then use every item of this set to gather the same date's time from the object.

update google chart with selected period parameter from v-select

I am looking for a way to update the google bar type chart display
by selecting the period value from v-select.
so far, Initially, my script loaded, call 'async created()' and
get Data from REST API to MongoDB call module and store returned data in posts[].
and then finally save it to chart data array.
The data format is like this
Key
Type
Date (YYYY-MM-DD)
String
success_count
int
failure_count
int
what I wanna do is, each v-select values consist with {name, id} pair.
Is there any way to pass selected 'selectedItem.id' to
script's created(parameter) method or PostService.get_dashboard_Posts(parameter)'s parameter position?
without refreshing the whole page?
below is my CSS code
<template>
<v-app
:style="{ background: $vuetify.theme.themes.light.background }"
style="max-height:100vw;"
>
<v-container style="min-height:100%;">
<v-layout row class="ma-4">
<v-app style="background-color:grey lighten-1:" class="rounded">
<v-row>
<v-col cols="12" md="12" class="ligne">
<v-container>
<v-row>
<v-col cols="12" md="12">
<h1 class="heading mb-2 grey--text">Dashboard </h1>
<v-card>
<template>
<p class="error" v-if="error">{{error}}</p>
<div class = "posts-container">
<div class="post"
v-for="(post,index) in posts"
v-bind:item="post"
v-bind:index="index"
v-bind:key="post._id"
>
</div>
</div>
<div id="app">
<v-col cols="12" md="12">
<v-row>
<v-col cols="12" md="9"><h3 class="subTitle">Number Of Transactions</h3></v-col>
<v-col cols="12" md="3">
<v-select
:items="comboItem"
v-model="selectedItem"
item-text="name"
item-value="id"
label="Period"
single-line
dense
v-on:change=""
return-object>
</v-select>
</v-col>
</v-row>
<GChart
type="ColumnChart"
:data="chartData"
:options="chartOptions"
/>
</v-col>
</div>
</template>
</v-card>
</v-col>
</v-row>
</v-container>
</v-col>
</v-row>
</v-app>
</v-layout>
</v-container>
below is my script code
<script>
// # is an alias to /src
import PostService from "../PostService";
export default {
name: "PostComponent",
data: () => ({
posts:[],
comboItem:[
{
name: 'today',
id: 1
},
{
name: 'last week',
id: 2
},
{
name: 'last month',
id: 3
},
{
name: 'last year',
id: 4
},
],
chartData: [
["date", "success", "failure"]
],
chartOptions: {
chart: {
title: "Company performance",
subtitle: "Sales,Expences,and Profit:2016-2019"
},
hAxis: {
title: "date",
textStyle: {
fontSize:9
}
},
vAxis: {
title: "frequency",
textStyle: {
fontSize:15
}
},
legend: {
position : 'top',
alignment:'center'
}
}
}),
async created() {
try {
this.posts = await PostService.get_dashboard_Posts();
for(var i=0;i<Object.keys(this.posts[0]).length;i++){
this.chartData.push([`${this.posts[0][i]._id.year}-${this.posts[0]
[i]._id.month}-${this.posts[0][i]._id.day}`, this.posts[0][i]._id.success_count, this.posts[0]
[i]._id.failure_count])
}
} catch (err) {
this.error = err.message;
}
},
components: {},
computed: {
theme() {
return this.$vuetify.theme.dark ? "dark" : "light";
}
},
methods:{
}
};
</script>
Yes that is possible,
Basically we need to be able to respond to changes to selectedItem. All the loading currently happens inside the created function, which we cannot run again, so step 1 would be to move or copy the body of the created function to a new method, which we may call load or something. Now we can respond to changed to selectedItem by calling load.
async created() {
await this.load();
},
components: {},
computed: {
theme() {
return this.$vuetify.theme.dark ? "dark" : "light";
}
},
methods: {
async load() {
try {
this.posts = await PostService.get_dashboard_Posts();
for (var i = 0; i < Object.keys(this.posts[0]).length; i++) {
this.chartData.push([`${this.posts[0][i]._id.year}-${this.posts[0]
[i]._id.month}-${this.posts[0][i]._id.day}`, this.posts[0][i]._id.success_count, this.posts[0]
[i]._id.failure_count
])
}
} catch (err) {
this.error = err.message;
}
}
}
There are a few ways we can respond to changes, i'll show you a few:
We can define this behavior on the input element directly in the template:
<v-select
v-model="selectedItem"
...
v-on:change="load(selectedItem.id)"
...
>
</v-select>
Another way to tell vue to respond to changes is by using watchers:
...
async created() {
await this.load();
},
watch: {
selectedItem(newValue) {
this.load(newValue.id);
// At the moment the watcher runs this.selectedItem is already
// the newValue (this.selectedItem === newValue) so we could also
// do this:
this.load(this.selectedItem.id);
}
},
components: {},
...
The last thing that I haven't covered and leave up to you is to supply the selectedItem.id to PostService.get_dashboard_Posts

Add new properties to an object used in v-for

I want to add data (from two v-text-fields) in my product component to my data that I pass to the server. So that if the user adds the values '444' and '230' to the v-text-fields, the entry would be:
{
"444": 230
}
So far, I have hard coded the '444' and managed to get the value '230' passed. But how do i pass both '444' and '230' according to user inputs from a v-text-field?
product.vue
<v-content>
<v-container>
<code>formData:</code> {{ formData }}<br />
<v-btn color="primary" #click="create">Save</v-btn>
(Check console)<br />
<v-row>
<v-col>
<v-text-field v-for="(value, key) in formData" :key="key"
label="Card Type" v-model="formData[key]"
></v-text-field>
</v-col>
</v-row>
</v-container>
</v-content>
data() {
return {
dialog: false,
product: null,
error: null,
formData: {
444: null,
}
}
},
methods: {
async create() {
try {
await ProductService.create(this.formData);
} catch (error) {
this.error = error.response.data.message
}
}
}
What changes would i need to make in my component so that formData is based on user input from another v-text-field?
I remember your previous question. To add a new item to that basic formData hash, you would do something like this:
<div>
<v-text-field label="Product #" v-model="newKey"></v-text-field>
<v-text-field label="Card Type" v-model="newValue"></v-text-field>
<v-btn #click="$set(formData, newKey, newValue)">+</v-btn>
</div>
This uses $set to add new properties to formData.

VuetifyJS Advanced slots autocomplete with Google Places API

I need to get VuetifyJS advanced slots to work with the Google Places API. Currently some addresses only show up in the autocomplete dropdown after clicking the "x" in the form field to delete the input text.
Here is a CodePen demonstrating the issue:
https://codepen.io/vgrem/pen/Bvwzza
EDIT: I just found out that populating the dropdown menu with the suggestions is the issue. The suggestions are visible in the console.log but not in the dropdown. Any ideas how to fix this issue?
(Some addresses work right away, some not at all - it's pretty random.
Any ideas on how to fix this are very welcome.)
JS:
new Vue({
el: "#app",
data: () => ({
isLoading: false,
items: [],
model: null,
search: null,
}),
watch: {
search(val) {
if (!val) {
return;
}
this.isLoading = true;
const service = new google.maps.places.AutocompleteService();
service.getQueryPredictions({ input: val }, (predictions, status) => {
if (status != google.maps.places.PlacesServiceStatus.OK) {
return;
}
this.items = predictions.map(prediction => {
return {
id: prediction.id,
name: prediction.description,
};
});
this.isLoading = false;
});
}
}
});
HTML:
<div id="app">
<v-app id="inspire">
<v-toolbar color="orange accent-1" prominent tabs>
<v-toolbar-side-icon></v-toolbar-side-icon>
<v-toolbar-title class="title mr-4">Place</v-toolbar-title>
<v-autocomplete
v-model="model"
:items="items"
:loading="isLoading"
:search-input.sync="search"
chips
clearable
hide-details
hide-selected
item-text="name"
item-value="symbol"
label="Search for a place..."
solo
>
<template slot="no-data">
<v-list-tile>
<v-list-tile-title>
Search for a <strong>Place</strong>
</v-list-tile-title>
</v-list-tile>
</template>
<template slot="selection" slot-scope="{ item, selected }">
<v-chip :selected="selected" color="blue-grey" class="white--text">
<v-icon left>mdi-map-marker</v-icon>
<span v-text="item.name"></span>
</v-chip>
</template>
<template slot="item" slot-scope="{ item, tile }">
<v-list-tile-avatar
color="indigo"
class="headline font-weight-light white--text"
>
{{ item.name.charAt(0) }}
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title v-text="item.name"></v-list-tile-title>
<v-list-tile-sub-title v-text="item.symbol"></v-list-tile-sub-title>
</v-list-tile-content>
<v-list-tile-action> <v-icon>mdi-map-marker</v-icon> </v-list-tile-action>
</template>
</v-autocomplete>
<v-tabs
slot="extension"
:hide-slider="!model"
color="transparent"
slider-color="blue-grey"
>
<v-tab :disabled="!model">Places</v-tab>
</v-tabs>
</v-toolbar>
</v-app>
</div>
(I enabled the relevant API's in Google Cloud.)
the problem is the number of request you do in a certain amount of time. Every character triggers an request to the GoogleApi which is resulting in.
I think the error_message isn't totally correct while I trying afterwards it gives me a result.
To solve this,
upgrade your GoogleApi account
Debounce your input. so wait till the customer is not typing for half a second and then sen a request to The googleApi. You could use lodash to implement to debounce functionality https://lodash.com/docs/#debounce

toLowerCase localeCompare does not work in NuxtJs VueJs Framework

I have encounter a very strange issue in NuxtJs (VueJs Framework).
I have a code which was working well for my stores list in alphabetical order and search filter. As it was working I copied the same code functionality for my categories. The next day when I click on categories it worked fine but on stores it gave me this two errors:
Cannot read property 'localeCompare' of undefined
Cannot read property 'toLowerCase' of undefined
Not sure what went wrong its the same code on both pages, only the difference in data it loads for stores and categories. If remove both localeCompare & toLowerCase it loads the data properly but without the search filter and the list unordered. Below is my code:
<script>
import axios from "axios";
export default {
asyncData({ req, params }) {
return axios.get(process.env.apiURL + "stores").then(res => {
return {
stores: res.data
};
});
},
data() {
return {
apiURL: process.env.apiURL,
active: null,
search: "",
Searched: "",
filterStores: [],
storeList: ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z']
}
},
methods: {
filterStore(n) {
this.tab = n;
axios.get(process.env.apiURL + 'stores?filter[where][store_name][regexp]=^\\b' + this.tab + '/i').then(res => {
return this.filterStores = res.data;
});
}
},
computed: {
AllStores: function() {
return this.stores.filter(store => {
return store.store_name.toLowerCase().includes(this.search.toLowerCase())
})
}
},
head: {
title: "Stores"
}
};
</script>
<template>
<v-container grid-list-md text-xs-center>
<v-text-field append-icon="search" v-model="search" id="filter" name="filter"
label="Search Your Favourite Store . . . ." auto-grow autofocus dont-fill-mask-blanks solo>
</v-text-field>
<br/>
<v-tabs grow dark color="teal accent-4" show-arrows slider-color="white">
<v-tab active>#</v-tab>
<v-tab v-for="n in storeList" :key="n" ripple #click="filterStore(n)">{{ n }}</v-tab>
<v-tab-item>
<v-layout row wrap>
<v-flex v-for="(store, index) in AllStores" :key="index" xs6 sm4 md2 lg2 xl3>
<v-card light>
<nuxt-link :to="'/store/'+store.store_name">
<v-card-media :src="`${apiURL}containers` + `${store.store_image}`" height="80px"></v-card-media>
<v-card-text class="blue">{{store.store_name}}</v-card-text>
</nuxt-link>
</v-card>
</v-flex>
</v-layout>
</v-tab-item>
</v-tabs>
<br/>
<v-layout row wrap>
<v-flex v-if="filterStores != ''" v-for="(store, index) in filterStores" :key="index" xs6 sm4 md2 lg2 xl3>
<v-card light>
<nuxt-link :to="'/store/'+store.store_name">
<v-card-media :src="`${apiURL}containers` + `${store.store_image}`" height="80px"></v-card-media>
<v-card-text class="blue">{{store.store_name}}</v-card-text>
</nuxt-link>
</v-card>
</v-flex>
<v-flex v-else>No Stores Available starting with this letter</v-flex>
</v-layout>
</v-container>
</template>
I tried to update my vue cli and reinstalled the modules and it worked, thanks

Categories

Resources