dynamic form funky behavior in vuejs

Viewed 42

I am using VueJS 2.6.11 and bootstrap 4 to create two dynamic sections(Category and Product) that contain divs and input fields. The product section is nested within the category section. When someone clicks the New Category button another category should get generated. The same behavior should also happen when someone clicks the New Product button, another Product section should get generated, but only inside the current category section.

Issue:

When someone clicks the New Product button, the Add Product section will get generated inside all current Category sections. Also, v-model appears to bind to every product name input. When someone clicks the X button for a specific Product section one product section would get deleted from all current Category sections.

I'm not exactly sure why this is happening.

codepen: https://codepen.io/d0773d/pen/ExjbEpy

code:

<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">

<title>Create Categories and Products</title>

  <!-- New Category -->
  <button class="btn btn-success mt-5 mb-5"
          @click="addNewCategoryForm">
    New Category
  </button>

  <div class="card mb-3" v-for="(category, index) in categories">
    <div class="card-body">
      <span class="float-right" 
            style="cursor:pointer"
            @click="deleteCategoryForm">
        X
      </span>

      <h4 class="card-title">Add Category</h4>

      <div class="category-form">
        <input 
          type="text" 
          class="form-control mb-2" 
          placeholder="Category Name"
          v-model="category.name">
      </div>

      <!-- New Product -->
      <button class="btn btn-success mt-5 mb-5"
              @click="addNewProductForm">
        New Product
      </button>

      <div class="card mb-3" v-for="(product, index) in products">
        <div class="card-body">
          <span class="float-right" 
                style="cursor:pointer"
                @click="deleteProductForm">
            X
          </span>

          <h4 class="card-title">Add Product</h4>

          <div class="product-form">
            <input 
              type="text" 
              class="form-control mb-2" 
              placeholder="Product Name"
              v-model="product.name">
          </div>
        </div>
      </div>
    </div>
  </div>        
</div>

<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<script>
  var app = new Vue({
    el: '.container',

    data: {
      categories: [
        {
          name: '',
        }
      ],

      products: [
        {
          name: '',
        }
      ]
    },

    methods: {
      addNewCategoryForm () {
        this.categories.push({
          name: '',
        });
      },

      deleteCategoryForm (index) {
        this.categories.splice(index, 1);
      },

      addNewProductForm () {
        this.products.push({
          name: '',
        });
      },

      deleteProductForm (index) {
        this.products.splice(index, 1);
      },
    }
  });
</script>

1 Answers

The general problem is you don't specify which products belong to which category. So in your current code, all products belong to all categories.

I would suggest to instead nest the products inside the category object.

var app = new Vue({
  el: ".container",

  data: {
    categories: [{
      name: "",
      products: [{
        name: ""
      }]
    }]
  },

  methods: {
    addNewCategoryForm() {
      this.categories.push({
        name: "",
        products: []
      });
    },

    deleteCategoryForm(index) {
      this.categories.splice(index, 1);
    },

    addNewProductForm(index) {
      this.categories[index].products.push({
        name: ""
      });
    },

    deleteProductForm(categoryIndex, index) {
      this.categories[categoryIndex].products.splice(index, 1);
    }
  }
});
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://unpkg.com/bootstrap@4.4.1/dist/css/bootstrap.min.css">
<!-- Vue -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<title>Create Categories and Products</title>
<div class="container">
  <!-- New Category -->
  <button class="btn btn-success mt-5 mb-5" @click="addNewCategoryForm">
        New Category
      </button>

  <div class="card mb-3" v-for="(category, catIndex) in categories">
    <div class="card-body">
      <span class="float-right" style="cursor:pointer" @click="deleteCategoryForm">
            X
          </span>

      <h4 class="card-title">Add Category</h4>

      <div class="category-form">
        <input type="text" class="form-control mb-2" placeholder="Category Name" v-model="category.name">
      </div>

      <!-- New Product -->
      <button class="btn btn-success mt-5 mb-5" @click="addNewProductForm(catIndex)">
            New Product
          </button>

      <div class="card mb-3" v-for="(product, index) in category.products">
        <div class="card-body">
          <span class="float-right" style="cursor:pointer" @click="deleteProductForm(catIndex, index)">
                X
              </span>

          <h4 class="card-title">Add Product</h4>

          <div class="product-form">
            <input type="text" class="form-control mb-2" placeholder="Product Name" v-model="product.name">
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Related