Dynamically change select input in Bootstrap Vue - javascript

I am trying to set some objects in a Bootstrap-Vue form select which I get via JSON.
The JSON is made up of teacher objects from the following fields:
[
{
"id": 1,
"name": "John",
"surname": "Doe",
"email": "john.doe#gmail.com"
}
]
What I'm trying to do is put the name and surname in the select list, that is the full name.
I have already managed to do this via a computed property by processing the list.
But now I want that when I select a teacher, the list of courses is filtered according to the chosen teacher.
To do this I need the teacher's email, which I can't recover, having processed the teachers to get the full name.
Consequently, I can't even update the list of courses based on the teacher chosen.
This is the code for the template:
<b-form-group
id="input-group-3"
label="Docente:"
label-for="input-3"
>
<b-form-select
v-model="teacher"
:options="teachers"
value-field="item"
text-field="fullName"
required
#change="filterCourse"
></b-form-select>
<div class="mt-3">
Selected: <strong>{{ teacher }}</strong>
</div>
</b-form-group>
This is the script code:
import { mapGetters, mapActions } from "vuex";
export default {
data() {
return {
teacher: "",
course: "",
};
},
created: function() {
this.GetActiveTeachers();
this.GetActiveCourses();
},
computed: {
...mapGetters({
ActiveTeacherList: "StateActiveTeachers",
ActiveCourseList: "StateActiveCourses",
FilteredTeacherList: "StateTeacherByCourse",
FilteredCourseList: "StateCourseByTeacher",
}),
teachers: function() {
let list = [];
this.ActiveTeacherList.forEach((element) => {
let teacher = element.name + " " + element.surname;
list.push(teacher);
});
return list;
},
},
methods: {
...mapActions([
"GetActiveTeachers",
"GetActiveCourses",
"GetCourseByTeacher",
"GetTeacherByCourse",
"AssignTeaching",
]),
async filterCourse() {
const Teacher = {
teacherEmail: "john.doe#gmail.com", // For testing purpose
};
try {
await this.GetCourseByTeacher(Teacher);
} catch {
console.log("ERROR");
}
},
async filterTeacher() {
const Course = {
title: "Programming", // For testing purpose
};
try {
await this.GetTeacherByCourse(Course);
} catch {
console.log("ERROR");
}
},
},
};

You're currently using the simplest notation that Bootstrap Vue offers for form selects, an array of strings.
I suggest you switch to use their object notation, which will allow you to specify the text (what you show in the list) separately from the value (what's sent to the select's v-model).
This way, you'll be able to access all the data of the teacher object that you need, while still being able to display only the data you'd like.
We can do this by swapping the forEach() in your teachers computed property for map():
teachers() {
return this.ActiveTeacherList.map((teacher) => ({
text: teacher.name + " " + teacher.surname,
value: teacher
}));
},
Then, all you need to do is update your filterCourse() handler to use the new syntax, eg.:
async filterCourse() {
const Teacher = {
teacherEmail: this.teacher.email,
};
try {
await this.GetCourseByTeacher(Teacher);
} catch {
console.log("ERROR");
}
},
As a final note, if you don't want or need the full object as the value, then you can mold it to be whatever you need, that's the beauty of this syntax.
For example, you want the full name and email, instead of the parts:
value: {
fullName: teacher.name + " " + teacher.surname,
email: teacher.email
}

Here's two different options you can do.
One would be to generate the <option>'s inside the select yourself, using a v-for looping over your teachers, and binding the email property to the value, and displaying the name and surname inside the option.
This will make your <b-select>'s v-model return the chosen teachers e-mail, which you can then use in your filter.
new Vue({
el: '#app',
data() {
return {
selectedTeacher: null,
activeTeachers: [{
"id": 1,
"name": "Dickerson",
"surname": "Macdonald",
"email": "dickerson.macdonald#example.com"
},
{
"id": 2,
"name": "Larsen",
"surname": "Shaw",
"email": "larsen.shaw#example.com"
},
{
"id": 3,
"name": "Geneva",
"surname": "Wilson",
"email": "geneva.wilson#example.com"
}
]
}
}
})
<link href="https://unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.js"></script>
<div id="app">
<b-select v-model="selectedTeacher">
<option v-for="teacher in activeTeachers" :value="teacher.email">
{{ teacher.name }} {{ teacher.surname }}
</option>
</b-select>
{{ selectedTeacher }}
</div>
The other option would be to change your computed to return an array of objects instead of simple strings as you're currently doing.
By default <b-select> expects the properties value and text if you use an array of objects in the options prop.
Here you would bind the email for each teacher to the value, and the name and surname to the text prop.
This will make your <b-select>'s v-model return the chosen teachers e-mail, which you can then use in your filter.
Reference: https://bootstrap-vue.org/docs/components/form-select#options-property
new Vue({
el: '#app',
data() {
return {
selectedTeacher: null,
activeTeachers: [{
"id": 1,
"name": "Dickerson",
"surname": "Macdonald",
"email": "dickerson.macdonald#example.com"
},
{
"id": 2,
"name": "Larsen",
"surname": "Shaw",
"email": "larsen.shaw#example.com"
},
{
"id": 3,
"name": "Geneva",
"surname": "Wilson",
"email": "geneva.wilson#example.com"
}
]
}
},
computed: {
teacherOptions() {
return this.activeTeachers.map(teacher => ({
value: teacher.email,
text: `${teacher.name} ${teacher.surname}`
}));
}
}
})
<link href="https://unpkg.com/bootstrap#4.5.3/dist/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css" rel="stylesheet" />
<script src="https://unpkg.com/vue#2.6.12/dist/vue.min.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.js"></script>
<div id="app">
<b-select v-model="selectedTeacher" :options="teacherOptions"></b-select>
{{ selectedTeacher }}
</div>

Related

How to write, update local JSON file in vue js

I have tried to use file system (fs module) but, not getting that how to use that FS module.
could anyone explain with proper example.
I was doing an example in vue js.
Create component
Added json file
Pass records as props on component
listen that props on another component
and render student list and perform remove record
Now next i want to Pass updated list using emit & update the JSON as well
This code is working properly just i want to update local json file after delete record.
StudentRegistration.vue
<template>
<div class="container">
<div class="row">
<template v-if="students.length">
<!-- {{student.length}} -->
<studentCard class="student__parent" v-for="student in students" :key="student.id" :student="student" #studentRemoveId="studentRemoveId"/>
</template>
<template v-else>
<div class="not__available">
Data not available
</div>
</template>
</div>
</div>
</template>
<script>
import studentCard from './studentCard.vue'
import studentData from '../data/StudentData.json'
export default {
name: 'StudentRegi',
components:{
studentCard
},
data() {
return {
students: studentData,
}
},
methods: {
studentRemoveId(studid){
this.students = this.students.filter( item => {
console.log('hey there ::=> ', item);
return item.id !== studid.id
});
}
},
}
</script>
studentCard.vue
<template>
<div>
<span class="student__remove" #click="removeStudent(student)">X</span>
<div class="student__inner">
<div class="student__image">
<img :src="student.avatar" alt="">
</div>
<div class="student__name student-detail">
<div>Name</div>
<div>{{student.fname}} {{student.lname}}</div>
</div>
<div class="student__age student-detail">
<div>Age</div>
<div>{{student.age}}</div>
</div>
<div class="studet_email student-detail">
<div>Email</div>
<div>{{student.email}}</div>
</div>
<div class="student__phone student-detail">
<div>Phone</div>
<div>{{student.phone}}</div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'StudentCard',
props:['student'],
methods: {
removeStudent(studid){
this.$emit('studentRemoveId', studid);
}
},
}
</script>
data.json
[
{
"id": 1,
"fname": "Atul",
"lname": "Bhavsar",
"age": "10 Years",
"email": "atul#gmail.com",
"phone": "9685958698",
"avatar": "https://i.postimg.cc/d3ykpLs8/stud1.jpg"
},
{
"id": 7,
"fname": "Foram",
"lname": "Dobariya",
"age": "20 Years",
"email": "sanju#gmail.com",
"phone": "9856985698",
"avatar": "https://i.postimg.cc/QtWp5XBn/stud7.jpg"
},
]
Hey After some searches I found a solution so sharing here it may help who stuck related to this issue,
As i show above code i just wanted to add code where i can write my json file. so i have made some changes in code, that is as below.
Add command in CMD :
json-server --watch './src/data/studentData.json' --port 3001
then it will provide local link from where you can open you json data
Resources
http://localhost:3001/studente
see below image :
StudentRegistration.vue
this code should be in script tag in StudentRegistration.vue component
In this file added below code added code in method and created create method
export default {
name: 'StudentRegi',
components:{
studentCard
},
data() {
return {
studentsGetData: []
// studentsGetData: studentData,
}
},
methods: {
studentRemoveId(studid){
fetch(`http://localhost:3001/studente/${studid.id}`,
{
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then(resp => {
console.log('res ::=> ', resp.data);
resp.data;
}).catch(error => {
console.log(error);
});
this.studentsGetData = this.studentsGetData.filter( item => {
return item.id !== studid.id
});
}
},
async created(){
await fetch('http://localhost:3001/studente')
.then(res => res.json())
.then(data => {
console.log('data ::=> ', data);
this.studentsGetData = data
} )
.catch(err => console.log(err.message))
}
}
Now Run your project

how to v-model on different array vue 3

Hi I was trying to use a v-model an input to a value in object in an array of object in Vue 3. The complexity lies in the fact the object is first processed by a function. And that it need to be processed every time when a change is made to an input. Here is my code (and a sandbox link) :
<template>
<div id="app">
<div v-for="param in process(parameters)" :key="param">
Name : {{param.name}} Value : <input v-model="param.value">
</div>
{{parameters}}
</div>
</template>
<script>
export default {
name: "App",
data(){
return{
parameters :[
{'name':'Richard Stallman','value':'cool dude'},
{'name':'Linus Torvalds','value':'very cool dude'},
{'name':'Dennis Ritchie','value':'very very cool dude'}
]
}
},
methods:{
process(parameters){
const results = parameters.map( param =>{
return {name:param.name+' extra text',
value:param.value+' extra text',
}
})
return results
}
}
};
</script>
I just want the original parameters to change when something is types in the inputs. Maybe #change could be of use. But I didn't find a fix with #change. Does anyone know a solution to my problem? Thanks in advance.
Use computed property to get reactive state of the data.
Working Demo :
new Vue({
el: '#app',
data: {
parameters :[
{'name':'Richard Stallman','value':'cool dude'},
{'name':'Linus Torvalds','value':'very cool dude'},
{'name':'Dennis Ritchie','value':'very very cool dude'}
]
},
computed: {
process() {
const results = this.parameters.map((param) => {
return {
name: param.name + ' extra text',
value: param.value + ' extra text'
}
});
return results;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="param in process" :key="param">
Name : {{param.name}}
Value : <input v-model="param.value">
</div><br>
<strong>Orinigal Data :</strong> {{parameters}}
</div>
I am not entirely sure I understood whether the person should be able to see/edit the text you added within you processing method.
Anyway, I think this sample of code should solve you problem :
<template>
<div id="app">
<div v-for="param in parameters" :key="param.name">
Name : {{ param.name }} Value : <input v-model="param.value" />
</div>
{{ process }}
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
parameters: [
{ name: "Richard Stallman", value: "cool dude" },
{ name: "Linus Torvalds", value: "very cool dude" },
{ name: "Dennis Ritchie", value: "very very cool dude" },
],
};
},
computed: {
process: function() {
const results = this.parameters.map((param) => {
return {
name: param.name + " extra text",
value: param.value + " extra text",
};
});
return results;
},
},
};
</script>
So, we're iterating through the parameters array directly, adding an input on the value just like you did.
When you type in the input, you update the parameter linked to it, in live.
I just switched the method you made into a computed method.
This way, every time parameters is updated, "process" is also updated because it's depending on it directly.
I also removed passing the "parameters" argument, it's in the component data, you can just access it directly.
This way, using "process" just like any variable, you'll always have the updated parameters + what you added to em.

#firebase/database: FIREBASE WARNING: Exception was thrown by user callback. TypeError: Cannot read properties of undefined (reading 'AccountStatus')

Few minutes ago, my application was running ok. Suddenly, variables start to get undefined and I get error messages. I had tried to build this app using laravel but kept having issues. Now I am working with Vue.js. These are my codes below:
service.js
import firebase from "../firebase";
// var whereis = "Deliverers.Profile";
const db = firebase.ref("/Deliverers");
class Service {
getAll() {
return db;
}
// create(tutorial) {
// return db.push(tutorial);
// }
update(key, value) {
return db.child(key).update(value);
}
// delete(key) {
// return db.child(key).remove();
// }
// deleteAll() {
// return db.remove();
// }
}
export default new Service();
user.vue
<template>
<div v-if="currentTutorial" class="edit-form">
<h4>Tutorial</h4>
<form>
<div class="form-group">
<label for="title">Title</label>
<input
type="text"
class="form-control"
id="title"
v-model="currentTutorial.user['FullName']"
/>
</div>
<div class="form-group">
<label for="description">Description</label>
<input
type="text"
class="form-control"
id="description"
v-model="currentTutorial.user['description']"
/>
</div>
<div class="form-group">
<label><strong>Status:</strong></label>
<!-- {{ currentTutorial.status ? "Published" : "Pending" }} -->
{{ currentTutorial.status }}
</div>
</form>
<button
class="badge badge-primary mr-2"
v-if="currentTutorial.published"
#click="updatePublished(false)"
>
UnPublish
</button>
<button
v-else
class="badge badge-primary mr-2"
#click="updatePublished(true)"
>
Publish
</button>
<button class="badge badge-danger mr-2" #click="deleteTutorial">
Delete
</button>
<button type="submit" class="badge badge-success" #click="updateTutorial">
Update
</button>
<p>{{ message }}</p>
</div>
<div v-else>
<br />
<p>Please click on a Tutorial...</p>
</div>
</template>
<script>
import Service from "../services/service";
export default {
name: "tutorial",
props: ["tutorial"],
data() {
return {
currentTutorial: null,
message: "",
};
},
watch: {
tutorial: function(tutorial) {
this.currentTutorial = { ...tutorial };
this.message = "";
},
},
methods: {
updatePublished(status) {
Service.update(this.currentTutorial.key, {
published: status,
})
.then(() => {
this.currentTutorial.published = status;
this.message = "The status was updated successfully!";
})
.catch((e) => {
console.log(e);
});
},
updateTutorial() {
const data = {
title: this.currentTutorial.title,
// description: this.currentTutorial.description,
};
Service.update(this.currentTutorial.key, data)
.then(() => {
this.message = "The tutorial was updated successfully!";
})
.catch((e) => {
console.log(e);
});
},
deleteTutorial() {
Service.delete(this.currentTutorial.key)
.then(() => {
this.$emit("refreshList");
})
.catch((e) => {
console.log(e);
});
},
},
mounted() {
this.message = "";
this.currentTutorial = { ...this.tutorial }
},
};
</script>
userlist.vue
EDITED: To replace the faulty function with a working version.
<template>
<div class="list row">
<div class="col-md-6">
<h4>Tutorials List</h4>
<ul class="list-group">
<li
class="list-group-item"
:class="{ active: index == currentIndex }"
v-for="(tutorial, index) in tutorials"
:key="index"
#click="setActiveTutorial(tutorial, index)"
>
{{ tutorial.user.Email}}
</li>
</ul>
<button class="m-3 btn btn-sm btn-danger" #click="removeAllTutorials">
Remove All
</button>
</div>
<div class="col-md-6">
<div v-if="currentTutorial">
<tutorial-details
:tutorial="currentTutorial"
#refreshList="refreshList"
/>
</div>
<div v-else>
<br />
<p>Please click on a Tutorial...</p>
</div>
</div>
</div>
</template>
<script>
import Service from "../services/service";
import TutorialDetails from "./user";
export default {
name: "tutorials-list",
components: { TutorialDetails },
data() {
return {
tutorials: [],
currentTutorial: null,
currentIndex: -1
};
},
methods: {
// onDataChange() {
// snapshot => {
// let data = snapshot.val();
// let _tutorials = [];
// Object.keys(data).forEach(key => {
// _tutorials.push({
// key: key,
// username: data[key].profile.fullname,
// // text: data[key].text}
// });
// });
// this.tutorials = _tutorials;
// };
// },
//THIS METHOD WORKED!!
onDataChange(snapshot) {
let data = snapshot.val();
let _tutorials = [];
Object.keys(data).forEach(key => {
_tutorials.push({
key: key,
user: data[key].Profile,
});
});
this.tutorials = _tutorials;
},
refreshList() {
this.currentTutorial = null;
this.currentIndex = -1;
},
setActiveTutorial(tutorial, index) {
this.currentTutorial = tutorial;
this.currentIndex = index;
},
removeAllTutorials() {
Service.deleteAll()
.then(() => {
this.refreshList();
})
.catch((e) => {
console.log(e);
});
},
},
mounted() {
Service.getAll().on("value", this.onDataChange);
// Service.getAll().on("value", function(snapshot){
// var data = snapshot.val();
// for(let i in data){
// console.log(data[i].Profile.Email);
// }
// });
},
beforeDestroy() {
Service.getAll().off("value", this.onDataChange);
}
};
</script>
Console.log(data) gives this result - which is what I expect it to do:
{AccountStatus: 'Verified', Address: 'Abakaliki', BirthYear: '1999', DeliveryOn: false, Email: 'www#gmail.com', …}
userslist.vue?8b1e:73
{AccountStatus: 'Unverified', Address: 'Amaeke Ekoli Edda Afikpo South LGA Ebonyi state', BirthYear: '1985', Date: 1640599050717, DeliveryOn: false, …}
userslist.vue?8b1e:73
{AccountStatus: 'Verified', Address: '7 glibert street kpirikpiri abakalik', BirthYear: '1973', Date: 1638213915413, DeliveryOn: false, …}
console.log(data.AccountStatus) gives the right results as shown below:
Verified
Unverified
Verified
console.log("items is:\n", JSON.stringify(items, null, 2)); gives the following output
items is:
{
"1UWuDwL2WvWndqgUBweoUjeEEZk1": {
"Bank details": {
"accountName": "Samuel Ankkk",
"accountNumber": "234567890",
"bank": "wensBANK"
},
"Location": {
"latitude": "9.0691414",
"longitude": "7.4a424",
"state": "Abuja"
},
"Profile": {
"AccountStatus": "Verified",
"Address": "Area B Opp Living Faith Church ",
"BirthYear": "1984",
"DeliveryOn": false,
"Email": "hyu#gmail.com",
"From": "Federal Capital Territory",
"Fullname": "Samuel jui",
"IdImage": "https://firebasestorage.googleapis.com/v0/b/lombaz-3490e.appspot.com/o/ID_Pictures%2FNational%20ID%2F1612350737409.jpg?alt=media&token=640b2544-a9af-4f86-b7d8-3d47f638f77f",
"Image": "https://firebasestorage.googleapis.com/v0/b/lombaz-3490e.appspot.com/o/Profile%20Pictures%2F1612350733949.jpg?alt=media&token=617d6b6b-50bd-4898-abdc-be53e70612bd",
"Phone_number": "08062093434",
"Route": "Jibowu",
"State": "Kogi State",
"To": "Lagos State",
"carrier": "Traveller || Individual",
"routed": "Federal Capital Territory - Jibowu"
}
},
"1WAVu8OUYzN7EudSc4vs55GUGYg1": {
"Profile": {
"AccountStatus": "Verified",
"Address": "ohuru amangwu obingwa ogbohill Aba Abia state nigeria",
"BirthYear": "1994",
"Date": 1631033704355,
"DeliveryOn": false,
"Email": "kui#gmail.com",
"Fullname": "Sunday vh",
"IdImage": "https://firebasestorage.googleapis.com/v0/b/lombaz-3490e.appspot.com/o/ID_Pictures%2FOthers%2F1631033692952.jpg?alt=media&token=71967d4d-4a7f-4669-a7e1-9e2968970c2f",
"Image": "https://firebasestorage.googleapis.com/v0/b/lombaz-3490e.appspot.com/o/Profile%20Pictures%2F1631033678872.jpg?alt=media&token=71f2f23c-ffa7-4bb7-a82a-d16949e0971f",
"Phone_number": "09064491585",
"Route": "None",
"State": "Abia State",
"routed": "None"
}
},
"1sLWwcxZL8Sr1sBFAzuPDcl1iXs1": {
"Bank details": {
"accountName": "Ekoh Jety",
"accountNumber": "67808870",
"bank": "first Bank"
},
"Location": {
"latitude": "5.3872821",
"longitude": "7.0089u9u8"
},
"Profile": {
"AccountStatus": "Verified",
"Address": "No 2 Obuagu, Enugu",
"BirthYear": "1998",
"DeliveryOn": false,
"Email": "j.fffg#gmail.com",
"From": "Enugu State",
"Fullname": "Ekoh wwiiwC.",
"IdImage": "https://firebasestorage.googleapis.com/v0/b/lombaz-3490e.appspot.com/o/ID_Pictures%2FNational%20ID%2F1609831737206.jpg?alt=media&token=8290066b-73ab-44d0-8807-c28af6592b0e",
"Image": "https://firebasestorage.googleapis.com/v0/b/lombaz-3490e.appspot.com/o/Profile%20Pictures%2F1609831730721.jpg?alt=media&token=6efe967a-58eb-4fa7-b841-81d3d7666eb1",
"Phone_number": "08100489261",
"Route": "Emene Axis (Enugu Int’l Airport)",
"State": "Enugu State",
"To": "Enugu State",
"carrier": "Traveller || Individual",
"routed": "Enugu State - Emene Axis (Enugu Int’l Airport)"
}
}
}
On the console, Console.log(data) displays fine. Then this warning and error follows:
index.esm.js?abfd:106 [2022-02-05T15:39:46.784Z] #firebase/database: FIREBASE WARNING: Exception was thrown by user callback. TypeError: Cannot read properties of undefined (reading 'AccountStatus')
at eval (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/userslist.vue?vue&type=script&lang=js&:84:24)
at eval (webpack-internal:///./node_modules/#firebase/database/dist/index.esm.js:4345:20)
at LLRBNode.inorderTraversal (webpack-internal:///./node_modules/#firebase/database/dist/index.esm.js:2697:15)
at LLRBNode.inorderTraversal (webpack-internal:///./node_modules/#firebase/database/dist/index.esm.js:2696:27)
at LLRBNode.inorderTraversal (webpack-internal:///./node_modules/#firebase/database/dist/index.esm.js:2698:24)
at LLRBNode.inorderTraversal (webpack-internal:///./node_modules/#firebase/database/dist/index.esm.js:2696:27)
at SortedMap.inorderTraversal (webpack-internal:///./node_modules/#firebase/database/dist/index.esm.js:3147:27)
at ChildrenNode.forEachChild (webpack-internal:///./node_modules/#firebase/database/dist/index.esm.js:3756:35)
at DataSnapshot.forEach (webpack-internal:///./node_modules/#firebase/database/dist/index.esm.js:4344:31)
at VueComponent.onDataChange (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/userslist.vue?vue&type=script&lang=js&:76:13)
defaultLogHandler # index.esm.js?abfd:106
Logger.warn # index.esm.js?abfd:212
warn # index.esm.js?e947:348
eval # index.esm.js?e947:704
setTimeout (async)
exceptionGuard # index.esm.js?e947:698
EventList.raise # index.esm.js?e947:9565
EventQueue.raiseQueuedEventsMatchingPredicate_ # index.esm.js?e947:9519
EventQueue.raiseEventsForChangedPath # index.esm.js?e947:9503
Repo.onDataUpdate_ # index.esm.js?e947:12730
PersistentConnection.onDataPush_ # index.esm.js?e947:12006
PersistentConnection.onDataMessage_ # index.esm.js?e947:12000
Connection.onDataMessage_ # index.esm.js?e947:11246
Connection.onPrimaryMessageReceived_ # index.esm.js?e947:11240
eval # index.esm.js?e947:11141
WebSocketConnection.appendFrame_ # index.esm.js?e947:10727
WebSocketConnection.handleIncomingFrame # index.esm.js?e947:10772
mySock.onmessage # index.esm.js?e947:10673
index.esm.js?e947:705 Uncaught TypeError: Cannot read properties of undefined (reading 'AccountStatus')
at eval (userslist.vue?8b1e:77:1)
at eval (index.esm.js?e947:4331:1)
at LLRBNode.inorderTraversal (index.esm.js?e947:2683:1)
at LLRBNode.inorderTraversal (index.esm.js?e947:2682:1)
at LLRBNode.inorderTraversal (index.esm.js?e947:2684:1)
at LLRBNode.inorderTraversal (index.esm.js?e947:2682:1)
at SortedMap.inorderTraversal (index.esm.js?e947:3133:1)
at ChildrenNode.forEachChild (index.esm.js?e947:3742:1)
at DataSnapshot.forEach (index.esm.js?e947:4330:1)
at VueComponent.onDataChange (userslist.vue?8b1e:70:1)
eval # userslist.vue?8b1e:77
eval # index.esm.js?e947:4331
LLRBNode.inorderTraversal # index.esm.js?e947:2683
LLRBNode.inorderTraversal # index.esm.js?e947:2682
LLRBNode.inorderTraversal # index.esm.js?e947:2684
LLRBNode.inorderTraversal # index.esm.js?e947:2682
SortedMap.inorderTraversal # index.esm.js?e947:3133
ChildrenNode.forEachChild # index.esm.js?e947:3742
DataSnapshot.forEach # index.esm.js?e947:4330
onDataChange # userslist.vue?8b1e:70
onceCallback # index.esm.js?e947:4935
eval # index.esm.js?e947:4545
exceptionGuard # index.esm.js?e947:694
EventList.raise # index.esm.js?e947:9565
EventQueue.raiseQueuedEventsMatchingPredicate_ # index.esm.js?e947:9519
EventQueue.raiseEventsForChangedPath # index.esm.js?e947:9503
Repo.onDataUpdate_ # index.esm.js?e947:12730
PersistentConnection.onDataPush_ # index.esm.js?e947:12006
PersistentConnection.onDataMessage_ # index.esm.js?e947:12000
Connection.onDataMessage_ # index.esm.js?e947:11246
Connection.onPrimaryMessageReceived_ # index.esm.js?e947:11240
eval # index.esm.js?e947:11141
WebSocketConnection.appendFrame_ # index.esm.js?e947:10727
WebSocketConnection.handleIncomingFrame # index.esm.js?e947:10772
mySock.onmessage # index.esm.js?e947:10673
setTimeout (async)
exceptionGuard # index.esm.js?e947:698
EventList.raise # index.esm.js?e947:9565
EventQueue.raiseQueuedEventsMatchingPredicate_ # index.esm.js?e947:9519
EventQueue.raiseEventsForChangedPath # index.esm.js?e947:9503
Repo.onDataUpdate_ # index.esm.js?e947:12730
PersistentConnection.onDataPush_ # index.esm.js?e947:12006
PersistentConnection.onDataMessage_ # index.esm.js?e947:12000
Connection.onDataMessage_ # index.esm.js?e947:11246
Connection.onPrimaryMessageReceived_ # index.esm.js?e947:11240
eval # index.esm.js?e947:11141
WebSocketConnection.appendFrame_ # index.esm.js?e947:10727
WebSocketConnection.handleIncomingFrame # index.esm.js?e947:10772
mySock.onmessage # index.esm.js?e947:10673
Before this error, I could see a list of all the emails and clicking on each, displays other details.
Suddenly, everything went south. I have cleared cache, still nothing changed. In fact, nothing displays in Microsoft edge, no warning, nothing in console at all.The errors are seen on chrome.
Any help will be appreciated please. Thanks
Watch out for asynchronous processes
console.log(data) can have a confusing output, because what you see in the console is what the value of data is at the time you are looking, which may not have been the value at the time it was actually printed to the console.
To avoid this, I suggest changing the console.log to the following:
console.log(JSON.stringify(data,null,2));
Doing a JSON.stringify forces Javascript to evaluate the object on-the-spot and turn it into a string.
I bet you a virtual $10 bill that when you make that change, you will see that data is rubbish at that time.
Why I am confident that data is at fault
Your error message was:
TypeError: Cannot read properties of undefined (reading 'AccountStatus')
This means that you were asking Javascript to read undefined.AccountStatus. The only place you could be doing that, i.e. the only place the word "AccountStatus" exists in your code, is here:
let key = item.key;
let data = item.val().Profile;
console.log(data);
_tutorials.push({
key: key,
status: data.AccountStatus, // ☚
email: data.Email,
});
This means that data must be undefined.
So if what you were printing out looked good, that must be a later value of data.
Surely the parameter to onDataChange should be a snapshot, not an array?
onDataChange(snapshot) {
const items = snapshot.val()
console.log("items is:\n", JSON.stringify(items, null, 2));
let _tutorials = [];
items.forEach((item) => {
let key = item.key;
let data = item.Profile;
_tutorials.push({
key: key,
status: data.AccountStatus,
email: data.Email,
});
});
this.tutorials = _tutorials;
}
Solution: items is now an object, rather than an array!
You say the code was working before, i.e. you were able to run
items.forEach( )
That means items was (before you made the change to the database) an array. That way, the elements of items were the item value, which in turn have the correct properties, including .AccountStatus.
In the current state of your database, it is clear that items is not an array at all! It is an object.
An array would appear like this:
items is:
[
{
"Bank details": {
"accountName": "Samuel Ankkk",
"accountNumber": "234567890",
"bank": "wensBANK"
},
"Location": {
"latitude": "9.0691414",
"longitude": "7.4a424",
"state": "Abuja"
},
"Profile": {
"AccountStatus": "Verified",
"Address": "Area B Opp Living Faith Church ",
...
But what you are seeing is this:
items is:
{
"1UWuDwL2WvWndqgUBweoUjeEEZk1": {
"Bank details": {
"accountName": "Samuel Ankkk",
"accountNumber": "234567890",
"bank": "wensBANK"
},
"Location": {
"latitude": "9.0691414",
"longitude": "7.4a424",
"state": "Abuja"
},
"Profile": {
"AccountStatus": "Verified",
"Address": "Area B Opp Living Faith Church ",
...
In other words, an array looks like this:
[ { "key": value, "anotherKey": anotherValue},
{ "key": value2, ... },
...
]
This can be accessed with .forEach
In contrast, an object looks like this:
{
"itemKey1": { "key": value, "anotherKey": anotherValue},
"itemKey2": { "key": value2, ... }
}
It cannot be accessed by .forEach. That is the error you are seeing.
What I think happened
You unknowingly changed the structure of items in Firebase. Instead of writing an array (or equivalently an object with keys 0, 1, 2, 3, 4 etc) you are now writing a real object with string keys.
Solution
Convert the object to an array, for example like this. Change this line:
items.forEach((item) => {
to this:
Object.values(items).forEach((item) => {
This is assuming you don't need the key of the item, e.g. "1UWuDwL2WvWndqgUBweoUjeEEZk1". To me, this string looks like a Firebase User ID. If so, you probably do want to extract it. For example:
Object.keys(items).forEach(userId => {
const item = items[userId];

How to have parent data be updated by child component with multiple values

Code below.
I think I'm missing a crucial piece here. I've been through the docs and watched the entire vue2 step by step. Everything is making sense so far but I'm stuck on what seems to be a core piece. Any help would be appreciated. If this is totally wrong, please let me know, I'm not married to any of this stuff.
Desired functionality: There is an order Vue instance and it has line items.
On order.mounted() we hit an api endpoint for the order's data, including possible existing line items. If there are existing line items, we set that order data (this.lineitems = request.body.lineitems or similar). This part works fine and I can get the order total since the orders' line items are up to date at this point.
Each line item is an editable form with a quantity and a product . If I change the quantity or product of any line item, I want the child line-item component to notify the parent component that it changed, then the parent will update its own lineitems data array with the new value, and preform a POST request with all current line item data so the server side can calculate the new line item totals (many specials, discounts, etc). This will return a full replacement array for the order's line item data, which in turn would passed down to the line items to re-render.
Problems:
The line-items components "update..." methods are feeling obviously wrong, but my biggest issue is understanding how to get the parent to update its own line items data array with the new data. for instance
​
lineitems = [
{id: 1000, quantity: 3, product: 555, total: 30.00},
{id: 1001, quantity: 2, product: 777, total: 10.00}
]
If the second line item is changed to quantity 1, how do I get the parent's lineitems data to change to this? My main problem is that I don't know how the parent is suppose to know which of its own lineitems data array need to be modified, and how to grab the data from the changed child. I assume it came in via an event, via emit, but do I now need to pass around the primary key everywhere so I can do loops and compare? What if its a new line item and there is no primary key yet?
Mentioned above, I'm using the existing line item's DB primary key as the v-for key. What if I need a "new lineitem" that appends a blank lineitem below the existing ones, or if its a new order with no primary keys. How is this normally handled.
Is there a best practice to use for props instead of my "initial..." style? I assume just using $emit directly on the v-on, but I'm not sure how to get the relevant information to get passed that way.
This seems like the exact task that VueJS is suited for and I just feel like I keep chasing my tail in the wrong direction. Thanks for the help!
LineItem
Vue.component('line-item', {
props: ["initialQuantity", "initialProduct", "total"],
data () {
return {
// There are more but limiting for example
quantity: initialQuantity,
product: initialProduct,
productOptions = [
{ id: 333, text: "Product A"},
{ id: 555, text: "Product B"},
{ id: 777, text: "Product C"},
]
}
},
updateQuantity(event) {
item = {
quantity: event.target.value,
product: this.product
}
this.$emit('update-item', item)
},
updateProduct(event) {
item = {
quantity: this.quantity,
product: event.target.value
}
this.$emit('update-item', item)
}
template: `
<input :value="quantity" type="number" #input="updateQuantity">
<select :value="product" #input="updateProduct">
<option v-for="option in productOptions" v-bind:value="option.id"> {{ option.text }} </option>
</select>
Line Item Price: {{ total }}
<hr />
`
})
Order/App
var order = new Vue({
el: '#app',
data: {
orderPK: orderPK,
lineitems: []
},
mounted() {
this.fetchLineItems()
},
computed: {
total() {
// This should sum the line items, like (li.total for li in this.lineitems)
return 0.0
},
methods: {
updateOrder(item) {
// First, somehow update this.lineitems with the passed in item, then
fetch(`domain.com/orders/${this.orderPK}/calculate`, this.lineitems)
.then(resp => resp.json())
.then(data => {
this.lineitems = data.lineitems;
})
},
fetchLineItems() {
fetch(`domain.com/api/orders/${this.orderPK}`)
.then(resp => resp.json())
.then(data => {
this.lineitems = data.lineitems;
})
},
},
template: `
<div>
<h2 id="total">Order total: {{ total }}</h2>
<line-item v-for="item in lineitems"
#update-item="updateOrder"
:key="item.id"
:quantity="item.quantity"
:product="item.product"
:total="item.total"
></line-item>
</div>
`
})
Here's a list of problems in your attempt that would prevent it from displaying anything at all i.e.
quantity: initialQuantity, - surely you meant quantity: this.initialQuantity, ... etc for all the other such data
missing } for computed total
your line-item template is invalid - you have multiple "root" elements
And then there's some minor issues:
you want the #change handler for the select, not #input, if your code ran, you'd see the difference,
Similarly you want #change for input otherwise you'll be making fetch requests to change the items every keystroke, probably not what you'd want
So, despite all that, I've produced some working code that does all you need - mainly for my own "learning" though, to be fair :p
// ******** some dummy data and functions to emulate fetches
const products = [
{ id: 333, text: "Product A", unitPrice: 10},
{ id: 555, text: "Product B", unitPrice: 11},
{ id: 777, text: "Product C", unitPrice: 12},
];
let dummy = [
{id: 1, quantity:2, product: 333, total: 20},
{id: 2, quantity:3, product: 777, total: 36},
];
const getLineItems = () => new Promise(resolve => setTimeout(resolve, 1000, JSON.stringify({lineitems: dummy})));
const update = items => {
return new Promise(resolve => setTimeout(() => {
dummy = JSON.parse(items);
dummy.forEach(item =>
item.total = parseFloat(
(
item.quantity *
(products.find(p => p.id === item.product) || {unitPrice: 0}).unitPrice *
(item.quantity > 4 ? 0.9 : 1.0)
).toFixed(2)
)
);
let res = JSON.stringify({lineitems: dummy});
resolve(res);
}, 50));
}
//********* lineItem component
Vue.component('line-item', {
props: ["value"],
data () {
return {
productOptions: [
{ id: 333, text: "Product A"},
{ id: 555, text: "Product B"},
{ id: 777, text: "Product C"},
]
}
},
methods: {
doupdate() {
this.$emit('update-item', this.value.product);
}
},
template: `
<p>
<input v-model="value.quantity" type="number" #change="doupdate()"/>
<select v-model="value.product" #change="doupdate()">
<option v-for="option in productOptions" v-bind:value="option.id"> {{ option.text }} </option>
</select>
Line Item Price: {{ '$' + value.total.toFixed(2) }}
</p>
`
})
//********* Order/App
const orderPK = '';
var order = new Vue({
el: '#app',
data: {
orderPK: orderPK,
lineitems: []
},
mounted() {
// initial load
this.fetchLineItems();
},
computed: {
carttotal() {
return this.lineitems.reduce((a, {total}) => a + total, 0)
}
},
methods: {
updateOrder(productCode) {
// only call update if the updated item has a product code
if (productCode) {
// real code would be
// fetch(`domain.com/orders/${this.orderPK}/calculate`, this.lineitems).then(resp => resp.json())
// dummy code is
update(JSON.stringify(this.lineitems)).then(data => JSON.parse(data))
.then(data => this.lineitems = data.lineitems);
}
},
fetchLineItems() {
// real code would be
//fetch(`domain.com/api/orders/${this.orderPK}`).then(resp => resp.json())
// dummy code is
getLineItems().then(data => JSON.parse(data))
.then(data => this.lineitems = data.lineitems);
},
addLine() {
this.lineitems.push({
id: Math.max([this.lineitems.map(({id}) => id)]) + 1,
quantity:0,
product: 0,
total: 0
});
}
},
template: `
<div>
<h2 id="total">Order: {{lineitems.length}} items, total: {{'$'+carttotal.toFixed(2)}}</h2>
<line-item v-for="(item, index) in lineitems"
:key="item.id"
v-model="lineitems[index]"
#update-item="updateOrder"
/>
<button #click="addLine()">
Add item
</button>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
</div>
note: there may be some inefficient code in there, please don't judge too harshly, I've only been using vuejs for a week

vue.js computed property not triggered

Vue JS computed property is not triggered With this markup
<!-- language: lang-html -->
<p>£{{plant_price}}</p>
<div v-if="selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
<!-- language: lang-js -->
var app = new Vue({
el: '#vueApp',
data: {
selected: {
type: {a: '' , b: ''},
vehicle: '',
plant: {
}
},
computed: {
plant_price: function() {
if (this.selected.plant.variations.length > 0 ) {
var variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
...
selected.plant is populated by clicking on a plant - triggering the updateSelected method.
<div class="col-sm-4" v-for="(plant, i) in step2.plants">
<div v-on:click="updateSelected(plant)" ....
methods: {
updateSelected: function(plant) {
this.selected.plant = plant; // selected plant
if (this.selected.plant.variations.length > 0 ) {
this.selected.plant.selected_variation = this.selected.plant.variations[0].id; // set the selected ID to the 1st variation
I have checked through the debugger, and can see that all the correct properties are available.
selected:Object
type:Object
vehicle: "Truck"
plant:Object
id:26
price:"52"
regular_price:"100"
selected_variation:421
variations:Array[2]
0:Object
id:420
name:"small"
price:52000
regular_price:52000
1:Object
etc...
I have a computed property, which should update the plant_price based on the value of selected.plant.selected_variation.
I grab selected.plant.selected_variation and search through the variations to retrieve the price. If no variation exists, then the plant price is given.
I have a method on each product to update the selected plant. Clicking the product populates the selected.plant and triggers the computed plant_price to update the price (as the value of selected.plant.selected_variation has changed).
However, the computed plant_price is not triggered by the select. Selecting a new variant does what its supposed to, it updates selected.plant.selected_variation. Yet my plant_price doesn't seem to be triggered by it.
So I refactored my code by un-nesting selected.plant.selected_variation. I now hang it off the data object as
data = {
selected_variation: ''
}
and alter my computer property to reference the data as this.selected_variation. My computed property now works??? This makes no sense to me?
selected.plant.selected_variation isn't reactive and VM doesn't see any changes you make to it, because you set it after the VM is already created.
You can make it reactive with Vue.set()
When your AJAX is finished, call
Vue.set(selected, 'plant', {Plant Object})
There're two ways you can do it, what you are dealing with is a nested object, so if you want to notify the changes of selected to the others you have to use
this.$set(this.selected, 'plant', 'AJAX_RESULT')
In the snippet I used a setTimeout in the created method to simulate the Ajax call.
Another way you can do it is instead of making plant_price as a computed property, you can watch the changes of the nested properties
of selected in the watcher, and then update plant_price in the handler, you can check out plant_price_from_watch in the snippet.
Vue.component('v-select', VueSelect.VueSelect);
const app = new Vue({
el: '#app',
data: {
plant_price_from_watch: 'not available',
selected: {
type: {a: '' , b: ''},
vehicle: "Truck"
}
},
computed: {
plant_price() {
return this.setPlantPrice();
}
},
watch: {
selected: {
handler() {
console.log('changed');
this.plant_price_from_watch = this.setPlantPrice();
},
deep: true
}
},
created() {
setTimeout(() => {
this.$set(this.selected, 'plant', {
id: 26,
price: '52',
regular_price: '100',
selected_variation: 421,
variations: [
{
id: 420,
name: "small",
price: 52000,
regular_price: 52000
},
{
id: 421,
name: "smallvvsvsfv",
price: 22000,
regular_price: 22000
}
]
})
}, 3000);
},
methods: {
setPlantPrice() {
if (!this.selected.plant) {
return 'not available'
}
if (this.selected.plant.variations.length > 0 ) {
const variant = _.find(this.selected.plant.variations, {id: this.selected.plant.selected_variation });
return variant.price;
} else {
return this.selected.plant.price;
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.js"></script>
<div id="app">
<p>£{{plant_price}}</p>
<p>£{{plant_price_from_watch}}</p>
<div v-if="selected.plant && selected.plant.variations.length > 0 ">
<select v-model="selected.plant.selected_variation" class="form-control">
<!-- inline object literal -->
<option v-for="(variation, i) in selected.plant.variations" :selected="variation.id == selected.plant.selected_variation ? 'selected' : ''":value="variation.id">
{{variation.name}}
</option>
</select>
</div>
</div>

Categories