0

I'm initially rendering some objects from an API call to my database, they are serialized and look like this initially:

<h3>Messages on Database</h3>
    <p v-if="messages.length ===0">No Messages</p>
    <div class="msg" v-for="(msg, index) in messages" :key="index">
        <p class="msg-index">[{{index}}]</p>

        <p class="msg-subject" v-html="msg.subject" v-if="!msg.editing"></p>
        <p><input type="text" v-model="msg.subject" v-if="msg.editing" ></p>
        <p>{{msg.editing}}</p>
        <p class="msg-body" v-html="msg.body" v-show="!messages[index].editing"></p>
        <p><input type="text" v-model="msg.body" v-show="messages[index].editing" ></p>

        <input type="submit" @click="deleteMsg(msg.pk)" value="Delete" />
        <input type="submit" @click="EditMsg(index)" value="Edit" />
        <input type="submit" @click="updateMsg(msg.pk)" value="Update" />
    </div>
  </div>
</template>

<script>
export default {
  name: "Messages",
  data() {
    return {
      subject: "",
      msgBody: "",
      messages: [],
    };

},

each message looks like this:

enter image description here

notice that body, pk and subject are the Django model fields. Each item in the array represents a database object.

What I want to do using vue.js, is allow users to edit each item. If the user clicks the edit button for an item, I want to transform its element from p to input, and submit that to the database.

In order to allow editing of individual items, I need an editing field in each item in the array, so I'm doing this in my mounted() property:

  mounted() {
    this.fetchMessages();
  },
  methods: {
    fetchMessages() {
      this.$backend.$fetchMessages().then(responseData => {
        this.messages = responseData;
        this.messages.forEach(function (value) {
           value['editing'] = false;
        });
        console.log(this.messages);
      });
    }, 

Now, when I load up the array in my console, I see this:

enter image description here

So I assumed that now, when the user clicks the Edit button, EditMsg is called, and the fields will transform according to the v-if/v-show directives:

EditMsg(msgIdx) {
        this.messages[msgIdx].editing = true;
        console.log(this.messages);
    },

But that's not happening. What is actually happening is this: the editing flag for the item is changed to true in the console/vue-developer-tools window, but nothing changes in the HTML.

What am I missing?

Full code:

<template>
  <div class="hello">
    <img src='@/assets/logo-django.png' style="width: 250px" />
    <p>The data below is added/removed from the Postgres Database using Django's ORM and Restframork.</p>
    <br/>
    <p>Subject</p>
    <input type="text" placeholder="Hello" v-model="subject">
    <p>Message</p>
    <input type="text" placeholder="From the other side" v-model="msgBody">
    <br><br>
    <input type="submit" value="Add" @click="postMessage" :disabled="!subject || !msgBody">

    <hr/>
    <h3>Messages on Database</h3>
    <p v-if="messages.length ===0">No Messages</p>
    <div class="msg" v-for="(msg, index) in messages" :key="index">
        <p class="msg-index">[{{index}}]</p>

        <p class="msg-subject" v-html="msg.subject" v-if="!msg.editing"></p>
        <p><input type="text" v-model="msg.subject" v-if="msg.editing" ></p>
        <p>{{msg.editing}}</p>
        <p class="msg-body" v-html="msg.body" v-show="!messages[index].editing"></p>
        <p><input type="text" v-model="msg.body" v-show="messages[index].editing" ></p>

        <input type="submit" @click="deleteMsg(msg.pk)" value="Delete" />
        <input type="submit" @click="EditMsg(index)" value="Edit" />
        <input type="submit" @click="updateMsg(msg.pk)" value="Update" />
    </div>
  </div>
</template>

<script>
export default {
  name: "Messages",
  data() {
    return {
      subject: "",
      msgBody: "",
      messages: [],
    };
  },
  mounted() {
    this.fetchMessages();
  },
  methods: {
    fetchMessages() {
      this.$backend.$fetchMessages().then(responseData => {
        this.messages = responseData;
        this.messages.forEach(function (value) {
           value['editing'] = false;
        });
        console.log(this.messages);
      });
    },
    postMessage() {
      const payload = { subject: this.subject, body: this.msgBody };
      this.$backend.$postMessage(payload).then(() => {
        this.msgBody = "";
        this.subject = "";
        this.fetchMessages();
      });
    },
    deleteMsg(msgId) {
        this.$backend.$deleteMessage(msgId).then(() => {
            this.messages = this.messages.filter(m => m.pk !== msgId);
            this.fetchMessages();
        });
    },
    EditMsg(msgIdx) {
        this.messages[msgIdx].editing = true;
        console.log(this.messages);
    },
    updateMsg(msgId) {
        console.log(this.subject, this.msgBody);
        const payload = { subject: this.subject, body: this.msgBody };
        this.$backend.$putMessage(msgId, payload).then(() => {
            this.fetchMessages();
            }
        )
    }
  }
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
hr {
  max-width: 65%;
}

.msg {
  margin: 0 auto;
  max-width: 30%;
  text-align: left;
  border-bottom: 1px solid #ccc;
  padding: 1rem;
}

.msg-index {
  color: #ccc;
  font-size: 0.8rem;
  /* margin-bottom: 0; */
}

img {
  width: 250px;
  padding-top: 50px;
  padding-bottom: 50px;
}

</style>
2
  • not sure why this is happening, but check this fiddle it has almost the same code jsfiddle.net/ckb34a2r Commented Oct 3, 2018 at 8:58
  • I think it's happening because VueJS doesn't recognize array mutations through indexing, just not sure how to do my mutation differently. Also, your example works but doesn't apply in my case since I can't do so much boilerplate. Commented Oct 3, 2018 at 9:06

1 Answer 1

2

According to Vue internals:

Vue observes data by converting properties with Object.defineProperty. However, in ECMAScript 5 there is no way to detect when a new property is added to an Object, or when a property is deleted from an Object.

So, when you bind your response data to this.messages, any mutation to array properties is not considered reactive anymore by Vue.

Instead, if you enrich responseData properties before binding it to the Vue data properties, all the array stays reactive. I mean like this:

fetchMessages() {
 this.$backend.$fetchMessages().then(responseData => {
        let editableMessages = responseData;
        editableMessages.forEach(function (value) {
           value['editing'] = false;
        });
        this.messages = editableMessages;
      });
}

Here there is a small example based on your domain.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.