1

I just started working with Vue.js and I'm facing some issues with the Vue.js reactive datasources.

The following code should be able to add and remove a row containing a textfield and a textarea in the parent element.

ElementsView.vue

<template>
    <div>
        <label></label>
        <br>
        <small class='text-muted'></small>
        <br>
        <div class='marks_content'>
            <div class='row' v-for="(item, index) in marks" v-bind:key="index">

                <div class='col-md-12 mark_header_container'>
                    <div class="numerator-element">
                        <span class="numerator-content">{{ index + 1 }}</span>
                    </div>
                    <div class="remove-button" v-on:click="removeMark(index)">
                    </div>
                </div>

                <div class='col-md-6'>
                    <TextField :element-id='"term_textfield" + index' :message="item.term" type='text' placeholder="Kenmerk / element / fase"></TextField>
                </div>

                <div class='col-md-6'>
                    <TextField :element-id='"desc_textfield" + index ' :optional="true" :message="item.description" type='text' placeholder="Beschrijving (optioneel)" multiline="true"></TextField>
                </div>


            </div>
        </div>

        <div class='row'>

            <div class='col-md-6'>
               <div class="form-check">
                    <input type="checkbox" class="form-check-input" id="order_check">
                    <label class="form-check-label" for="order_check">Volgorde van belang (bij fases)</label>
                </div>
            </div>
            <div class='col-md-6 right_column'>
                <a href="#" v-on:click="addMark">Nieuwe rij toevoegen</a>
            </div>
        </div>
    </div>
</template>
<script>


import TextField from './TextField.vue'
export default {
    props: ['elements'],
    name: 'ElementsView', 
    components: {
        TextField
    },
    data() {
        return {
            marks: [{}]
        }
    },
    mounted() {
        if (this.elements) {
            if (this.elements.length > 0) {
                 this.marks = this.elements;
            }
        }

    },
    methods: {
        addMark: function (e) {
            this.marks.push({ 'term': '', 'description': ''}); //I thought the issue might be related to having undefined values, so I changed it to empty strings
        }, 
        removeMark: function(index) {
            //this.marks = [{ 'term': 'term1', 'description': 'desc1'}, { 'term': 'term12', 'description': 'desc13'}]

            this.marks.splice(index, 1);
        }
    }
}

</script>

TextField.vue

<template>
  <div class="form-group">
    <label v-if="title" :for="elementId">{{title}}</label>

    <textarea v-if="multiline" v-bind:class="{ optional: optional }" rows="1" :type="type" class="form-control" :id="elementId" :aria-describedby="elementId + '_help'"  :placeholder="placeholder" v-model="messageValue"></textarea>
    <Input :value="message" v-bind:class="{ optional: optional }" v-else rows="1" :type="type" class="form-control" :id="elementId" :aria-describedby="elementId + '_help'"  :placeholder="placeholder"/>

      <small v-if="help" :id="elementId + '_help'" class="form-text text-muted">{{help}}</small>
  </div>
</template>

<script>

import autosize from 'autosize';

export default {
  props: ['elementId', 'type', 'title', 'help', 'placeholder', 'multiline', 'message', 'optional'],
  name: 'TextField',
  data() {
      return {
          messageValue: this.message
      }
  },
  mounted() {
      var root = this.$el;
      var textAreas = root.getElementsByTagName("TextArea");
      if (textAreas.length > 0) {
            var area = textAreas[0];
            autosize(area);
        }
  }
}
</script>

I want to have an empty row at the starting point, indicated by the marks: [{}] in the code. Whenever a user wants to add a new row a new empty json object is added to the array. This works fine, however deleting the object causes errors.

The array consist of empty objects that are not bind to any input in the row elements, therefore the array consists of empty JSON objects. Because of this I would expect the interface to rerender the number of row elements - 1 after an element is deleted and do not show any text that was written by the user earlier.

This is not the case, the input that the user earlier entered remains visible even though the new objects are empty. This happens for both the addMark function (which is not problem, but not what I expected) and happens for the removeMark function (I see a random row disappear).

Originally I assumed that it was necessary to update the data that the list relies on before mutating it as this would force it to be redrawn with the array from the data object.

In order to test this assumption, I tried to assign a different array in the removeMark function (just for testing purposes) and see what happens. When the input is not modified this seems to work partly but when the input is modified this new array value is being overwritten.

Can someone please point out what I'm doing wrong?

1 Answer 1

3

The problem is this line:

<div class='row' v-for="(item, index) in marks" v-bind:key="index">

You are binding the array's index as identifier to the rendered entry. This won't work as the index changes for items when another item is deleted. This explains the seemingly random behavior. In fact the rerendering does not work correctly.

If you had a unique identifier you could use that. If the entries term is unique:

<div class='row' v-for="(item, index) in marks" v-bind:key="item.term">

If term is not the unique you can create an id yourself and use that as key:

<div class='row' v-for="(item, index) in marks" v-bind:key="item.id">

For a unique, throw away id the current timestamp is practical. Change addMark to create the id:

addMark: function (e) {
    this.marks.push({ 'term': '', 'description': '', id: (new Date()).valueOf() });
}
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.