So the problem I'm facing is that I have a template that fetches data from a Realtime Firebase Database and at the same time the user can import more data through an input
element. I'm using Vue.js and I need the data to be bound to each other.
Here is my template:
<template>
<ul>
<li>
<input type="text" v-model="email" v-on:keyup.enter="addData()"/>
<img @click="addData()" src="@/assets/Plus.png" />
</li>
</ul>
<ul>
<li v-for="(item, key) in emails" :key="key">
<div>
<p>{{ item.data }}</p>
<img @click="deleteDataByKey(key)" src="@/assets/Delete.png" />
</div>
<div class="first">
<input type="text" v-model="comment[key]" v-on:keyup.enter="addComment(key, comment[key])"/>
<img @click="addComment(key, comment[key])" src="@/assets/Plus.png" />
</div>
<div class="second">
<p>{{ comment[key] }}</p>
<img @click="deleteCommentByKey(key)" src="@/assets/Delete.png" />
</div>
</li>
</ul>
</template>
Now what is happening is that I want to show <div class="first">
when there is no comment and when there is one, <div class="second">
should be shown while hiding the first one.
I tried using v-if="comment[key]"
but it will toggle the divs straight away.
I also tried to v-model.lazy
which seems to be working but then the method to update the db is not called.
I tried using pure JS in the method to change the HTML but it doesn't seem to be working as well.
These are my methods and data:
data() {
return {
emailList: [],
email: "",
comment: []
};
},
addData() {
db.ref("emailItems").push({
data: data
});
this.email = "";
this.fetchData();
},
deleteDataByKey(key) {
db.ref("emailItems"+key).remove();
this.fetchData();
},
addComment(key, comment) {
db.ref(`emailItems/${key}/comment`).set(comment);
},
deleteCommentByKey(key){
db.ref("comment/"+key).remove();
this.fetchData();
},
fetchData() {
db.ref("emailItems")
.once("value")
.then(snapshot => {
this.emailList = snapshot.val().emailItems;
});
}
And the db structure looks like this
Any help would be highly appreciated...
I think you should build more on the (arguably) biggest features of Vue, namely: reactivity & components.
Break down the logic a bit more, until you arrive at elements that do only one thing - those elements can be your components. Then build up the business logic from those atomic components.
Vue.component('NewEmail', {
data() {
return {
email: null,
}
},
methods: {
handleKeyup() {
this.$emit("add-email", {
email: this.email,
comment: null
})
this.email = null
}
},
template: `
<div>
<label>
NEW EMAIL: <input
type="email"
placeholder="Type in an email"
v-model="email"
@keyup.enter="handleKeyup"
/>
</label>
</div>
`
})
Vue.component('SingleEmailRow', {
props: {
email: {
type: Object,
default: null,
}
},
methods: {
handleDeleteClick() {
this.$emit('remove-email', this.email)
},
},
template: `
<li
class="d-flex"
>
<div>
{{ email.email }}
</div>
<div>
<button
@click="handleDeleteClick"
>
X
</button>
</div>
<component
:is="email.comment ? 'HasEmailComment' : 'NoEmailComment'"
:email="email"
v-on="$listeners"
></component>
</li>
`
})
Vue.component('HasEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: null
})
}
},
template: `
<div
class="d-flex"
>
<div>
{{ email.comment }}
</div>
<button
title="Remove comment"
@click="handleUpdateComment"
>
-
</button>
</div>
`
})
Vue.component('NoEmailComment', {
props: {
email: {
type: Object,
required: true
}
},
data() {
return {
comment: null,
}
},
methods: {
handleUpdateComment() {
this.$emit('update:comment', { ...this.email,
comment: this.comment
})
}
},
template: `
<div
class="d-flex"
>
<div>
<input
v-model="comment"
type="text"
placeholder="Write a comment"
/>
</div>
<button
title="Add comment"
@click="handleUpdateComment"
>
+
</button>
</div>
`
})
new Vue({
el: "#app",
data() {
return {
emailList: [],
}
},
methods: {
handleAddEmail(newEmail) {
if (!this.emailList.find(({
email
}) => email === newEmail.email)) {
this.emailList.push(newEmail)
}
},
handleRemoveEmail(toRemove) {
this.emailList = this.emailList.filter(({
email
}) => {
return email !== toRemove.email
})
},
handleUpdateComment(newEmail) {
this.emailList = this.emailList.map(email => {
if (email.email === newEmail.email) {
return newEmail
} else {
return email
}
})
}
}
})
.d-flex {
display: flex;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<new-email @add-email="handleAddEmail"></new-email>
<ul v-for="(email, i) in emailList" :key="i">
<single-email-row :email="email" @remove-email="handleRemoveEmail" @update:comment="handleUpdateComment"></single-email-row>
</ul>
</div>
OK, the comment
handling in SingleEmailRow
could be put to its separate component (based on my thoughts above).
The main points in the snippet above are:
emailList
in the root component) that is passed down as props
where needed, and all the other components & functions just manipulate THAT list via events
(reactivity is great!)props
. (Ok, they have some internal state, but hey - this is only a snippet! :) )NewEmail
: to add an item to the central emailList
SingleEmailRow
: to manage the data in ONE email itemI hope this helps you a bit to reach a solution for your problem.
I had to update the snippet, because I wasn't satisfied with it.
Modifications:
HasEmailComment
, NoEmailComment
SingleEmailRow
with the two new componentsNow, all the components have only one task to do.
Your solution is working and the info you provided helped me a lot hence I'm accepting it as the correct answer. I was looking though more of a quick fix if there could be one. Now I need to rearrange the whole structure ..
FYI: I fixed it in another way. Basically I rendered the data coming from the db in the div rather than binding straight away the data from the input field. Thank you very much for your effort @gergely
@MakisMilas thanks for the response! I'm glad that I could help and that you found a solution!