toLowerCase localeCompare does not work in NuxtJs VueJs Framework - javascript

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

Related

Vue.js & vuex handling SMS by server-side-events

I have a app, which is basically a Call centrum. You can receive calls, call to someone, receive sms and send sms etc.. I have problem with showing my SMS on screen, when I receive event from backend, I am showing that data on screen using vuex and V-for for specific component. Problem is that when I receive another event from backend with different number, I would like to show it under that first sms, but it will overwrite first sms and show only that new sms. I was trying multiple approaches, but nothing worked for me so I hope someone will be able to show me my mistake.
Here is photo of screen with one sms (red box is where second sms should be with own informations like number...)..
Here is code where I receive events.
export default function setupStream(){
let evtSource = new EventSource('/hzs/events.sse');
evtSource.addEventListener('receive_sms', event => {
let sms_data = JSON.parse(event.data);
store.dispatch('receiveSMS', sms_data);
}, false)
}
Here is my vuex code
const state = {
sms: [],
};
const getters = {
getSMS: (state) => state.sms,
};
const actions = {
receiveSMS({ commit }, sms_data) {
commit('setSMS', sms_data);
},
};
const mutations = {
setSMS: (state, sms) => (state.sms = sms),
};
export default {
state,
getters,
actions,
mutations
}
And here is component.
<template>
<v-card>
<v-card-title class="primary white--text">
{{ $t("Communication") }}
</v-card-title>
<v-card d-flex flex-column height="100%" class="card-outter scroll">
<v-col>
<div v-for="sms in getSMS" :key="sms.id">
<v-card-actions>
<v-row>
<v-btn #click="openChat" icon class="mt-4"
><v-img
max-width="30px"
max-height="30px"
class="mt-2"
src="#/assets/icons/icon-sms.svg"
alt="icon-sms"
/></v-btn>
<v-col>
<span>{{sms.date_time}}</span> <br />
<h4>{{sms.sender}}</h4>
<!-- Dialog for Adding new Note -->
<v-dialog
v-model="showEditor"
max-width="400px"
persistent
scrollable
>
<template v-slot:activator="{ on, attrs }">
<v-btn
#click="showEditor = true"
depressed
small
v-bind="attrs"
v-on="on"
>{{$t("Add Note")}}</v-btn
>
</template>
<AddNoteDialog v-on:close-card="showEditor = false"
/></v-dialog>
</v-col>
<v-spacer></v-spacer>
<v-btn class="mt-5" icon #click="deleteCommunication"
><v-img
max-width="20px"
src="#/assets/icons/icon-delete.svg"
alt="icon-delete"
/></v-btn>
</v-row>
</v-card-actions>
<v-divider></v-divider>
</div>
<v-spacer></v-spacer>
<v-divider></v-divider>
<v-card-actions class="card-actions">
<v-row>
<v-text-field
class="ml-4"
color="primary white--text"
required
:label="$t('Mobile number')"
clearable
></v-text-field>
<v-dialog
v-model="showEditor1"
max-width="450px"
persistent
scrollable
>
<template v-slot:activator="{ on, attrs }">
<v-btn
#click="showEditor1 = true"
class="mt-5 mr-4"
depressed
icon
v-bind="attrs"
v-on="on"
><v-icon>mdi-plus-circle</v-icon></v-btn
>
</template>
<AddNummberDialog v-on:close-card="showEditor1 = false"
/></v-dialog>
</v-row>
</v-card-actions>
</v-col>
</v-card>
</v-card>
</template>
<script>
import AddNoteDialog from "#/components/UI/AddNoteDialog";
import AddNummberDialog from "#/components/UI/AddNummberDialog";
import { mapGetters, mapActions } from 'vuex';
export default {
name: "Communication",
data() {
return {
dialog: false,
showEditor: false,
showEditor1: false,
note: '',
chat: this.switchChat,
};
},
computed: {
...mapGetters(['getSMS']),
},
components: { AddNoteDialog, AddNummberDialog },
props: ["switchChat"],
methods: {
...mapActions(['setupEvents']),
openChat() {
this.$emit('openChat')
},
async deleteCommunication() {
alert("Deleted");
},
},
};
</script>
<style>
.scroll {
overflow-y: scroll;
}
.card-outter {
padding-bottom: 50px;
}
.card-actions {
position: absolute;
bottom: 0;
width: 100%;
}
</style>
I think that solution is creating new array, where I will store every single SMS that I receive. Problem is that I don't know how and where to do it.
You already have your vue state array called sms which is a good start. You'll need to update your Vuex to have an additional mutation called "addNewSMS" or something:
const mutations = {
setSMS: (state, sms) => (state.sms = sms),
addNewSMS: (state, newSMS) => state.sms.push(newSMS),
};
This will update your state.sms array to include more than one element, which you should be able to loop through using a v-for loop in your template.
Of course, you'll also need to update your actions like this:
const actions = {
receiveSMS({ commit }, sms_data) {
commit('addNewSMS', sms_data);
},
};
As a sidenote, I'd personally change the sms variable name to messages so its clearer to you and other coders that it contains multiple objects.

TextArea Avoid mutating a prop directly since the value will be overwritten

I know that a similar question has already been dealt with on stackoverflow. But I could not put together a solution from the proposed one. I am very ashamed.
The essence is this: I have a component and another one inside it.
The child component-VClipboardTextField is a ready-made configured text-area. I couldn't get the input out of there and I don't get an error when I try to enter it.
Avoid mutating a prop directly since the value will be overwritten
whenever the parent component re-renders. Instead, use a data or
computed property based on the prop's value. Prop being mutated:
"message"
code tabs-item.vue
<template>
<v-container fluid>
<v-row align="center">
<v-col cols="9">
<v-card flat>
<v-card-text>
<h1>Request</h1>
<v-container>
<v-textarea v-model="message"
placeholder="Placeholder"
label="Request"
auto-grow
clear-icon="mdi-close-circle-outline"
clearable
rows="10"
row-height="5"
#click:clear="clearMessage"
></v-textarea>
<v-textarea v-model="response"
placeholder="Placeholder"
label="Request2"
auto-grow
counter
rows="10"
row-height="5"
color="success"
></v-textarea>
<VClipboardTextField ></VClipboardTextField>
<VClipboardTextField isReadOnly></VClipboardTextField>
</v-container>
<v-row>
<v-btn
dark
color="primary"
elevation="12"
justify="end"
float-right
#click="sendRequest"
>
Send Request
</v-btn>
</v-row>
</v-card-text>
</v-card>
</v-col>
<v-col cols="3">
<schema-selector #changeSchema="onChangeSchema"></schema-selector>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
name: 'tabs-item',
props: ['response'],
data() {
return {
schema: String,
message: '',
}
},
methods: {
sendRequest() {
const message = {
message: this.message,
method: this.schema.state
}
this.$emit('sendRequest', message)
},
clearMessage() {
this.message = ''
},
onChangeSchema(selectS) {
console.log("get schema: ", selectS.state)
this.schema = selectS
}
},
}
</script>
and child VClipboardTextField.vue
<template>
<v-container>
<v-tooltip bottom
v-model="show">
<template v-slot:activator="{ on, attrs }">
<v-textarea
v-model="message"
:append-outer-icon="'mdi-content-copy'"
:readonly="isReadOnly"
auto-grow
filled
counter
clear-icon="mdi-close-circle-outline"
clearable
label="Response message"
type="text"
#click:append-outer="copyToBuffer"
#click:clear="clearMessage"
></v-textarea>
</template>
<span>Tooltip</span>
</v-tooltip>
</v-container>
</template>
<script>
export default {
name: 'VClipboardTextField',
props: {
isReadOnly: Boolean,
message : { type :String, default: "msg"}
},
data() {
return {
show: false,
// messageLocal: 'Response!',
iconIndex: 0,
}
},
methods: {
copyToBuffer() {
console.log("this: ", this)
navigator.clipboard.writeText(this.message);
this.toolTipChange()
setTimeout(() => this.toolTipChange(), 1000)
},
clearMessage() {
this.message = ''
},
toolTipChange() {
if (this.show)
this.show = false
}
}
}
</script>
I will be glad to see an example of the code that will explain how to correctly solve this problem without crutches!
Thanks.
you cannot modify props in components, if the initial value of message is needed as placeholder (meaning the input might not be empty at the begining), you can store the data in message prop to another data variable and use that as v-model to the textarea.
if there is no initial value for message, just use another data variable for textarea and emit an update for message in a watcher.
in code tabs-item.vue
<VClipboardTextField :message.sync="message"></VClipboardTextField>
and child VClipboardTextField.vue
<template>
<v-container>
<v-tooltip bottom
v-model="show">
<template v-slot:activator="{ on, attrs }">
<v-textarea
v-model="message_slug"
:append-outer-icon="'mdi-content-copy'"
:readonly="isReadOnly"
auto-grow
filled
counter
clear-icon="mdi-close-circle-outline"
clearable
label="Response message"
type="text"
#click:append-outer="copyToBuffer"
#click:clear="clearMessage"
></v-textarea>
</template>
<span>Tooltip</span>
</v-tooltip>
</v-container>
</template>
<script>
export default {
name: 'VClipboardTextField',
props: {
isReadOnly: Boolean,
message : { type :String, default: "msg"}
},
data() {
return {
show: false,
// messageLocal: 'Response!',
iconIndex: 0,
message_slug: '',
}
},
watch: {
message_slug(x) {
this.$emit('update:message', x)
}
},
}
</script>
It will bind the value to message_slug and update the message on parent component when it's value changes.
Instead of watching for change every time, you can only emit when there are changes.
In your tabs-item.vue
<VClipboardTextField v-model="message"></VClipboardTextField>
In your VClipboardTextField.vue component, you can receive the v-model input prop as a "VALUE" prop and assign it to a local data property. That way u can manipulate the local property and emit only when in it is changed!
<template>
<v-container>
<v-tooltip bottom v-model="show">
<template v-slot:activator="{ on, attrs }">
<v-textarea
v-model="message"
:append-outer-icon="'mdi-content-copy'"
:readonly="isReadOnly"
v-on="on"
v-bind="attrs"
auto-grow
filled
counter
clear-icon="mdi-close-circle-outline"
clearable
label="Response message"
type="text"
#click:append-outer="copyToBuffer"
#click:clear="clearMessage"
#change="$emit('input', v)"
></v-textarea>
</template>
<span>Tooltip</span>
</v-tooltip>
</v-container>
</template>
<script>
export default {
name: "VClipboardTextField",
props: {
isReadOnly: { type: Boolean },
value: {
type: String,
},
},
data() {
return {
show: false,
message: this.value,
};
},
};
</script>

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.

How to create array of strings in VueJS?

I am using VueJS and Vuetify to create a modal that can accept some strings in the text field. Now what i want to do is to push the input string inside an array on click. So let's say if i input something and click create the resultant array is ['inputValue1'] but if i add another value by separating with a comma, the resultant array should be ['inputValue1', 'inputValue2'] instead i am getting it as ['inputValue1', 'inputValue1' 'inputValue2']. So the new value should be pushed to the new index instead of adding it with the last value.
Here is a demo
new Vue({
el: "#app",
data() {
return {
dialog: false,
inputValue: "",
stringArray: []
};
},
methods: {
createArray() {
if (this.inputValue !== "") {
this.stringArray.push(this.inputValue);
console.log(this.stringArray);
}
},
closeDialog() {
this.dialog = false;
this.inputValue = "";
this.stringArray = [];
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#1.5.14/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-layout justify-center>
<v-flex>
<v-btn color="primary" #click="dialog=!dialog"> Click Me </v-btn>
</v-flex>
</v-layout>
<v-dialog v-model="dialog" width=350>
<v-card>
<v-card-title primary-title>
Create Array
</v-card-title>
<v-card-text>
<span class="title">How to create Array of Strings </span>
<v-layout justify-center>
<v-flex xs11>
<v-text-field v-model="inputValue"></v-text-field>
</v-flex>
</v-layout>
</v-card-text>
<v-card-actions class="mt-5">
<v-spacer></v-spacer>
<v-btn #click="closeDialog">CLOSE</v-btn>
<v-btn #click="createArray" :disabled="this.inputValue === ''" color="primary">CREATE</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-app>
</div>
Also on Cancel how can i set the input value and the array to an empty string and an empty array respectively. Thank You. I asked it yesterday but had to delete since i wasn't able to figure out the exact issue.
Your `createArray' method is not attached to any click event. Other than that the code is correct. :)
You should clear the inputValue after the value is pushed to the array like this:
methods: {
createArray() {
if (this.inputValue !== "") {
this.stringArray.push(this.inputValue);
this.inputValue = '';
console.log(this.stringArray);
} else {
console.log('The inputValue is empty')
}
},
closeDialog() {
this.dialog = false;
this.inputValue = "";
this.stringArray = []
}
}
});

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

Categories

Resources