Warm tip: This article is reproduced from serverfault.com, please click

How to toggle between DIVs with bound data in Vue?

发布于 2020-11-27 23:16:13

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

enter image description here

Any help would be highly appreciated...

Questioner
Makis Milas
Viewed
0
muka.gergely 2020-11-28 20:04:36

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:

  • there's only one original data source (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!)
  • because all the components are based on one central data source, they just have to work with the item they get as props. (Ok, they have some internal state, but hey - this is only a snippet! :) )
  • the two components have only one responsibility:
    • NewEmail: to add an item to the central emailList
    • SingleEmailRow: to manage the data in ONE email item

I hope this helps you a bit to reach a solution for your problem.

EDIT: UPDATING SNIPPET

I had to update the snippet, because I wasn't satisfied with it.

Modifications:

  • adding two new components: HasEmailComment, NoEmailComment
  • updated SingleEmailRow with the two new components

Now, all the components have only one task to do.