2

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>

dottedquad
  • 1,371
  • 6
  • 24
  • 55
  • 1
    A sideline note. If you're planning to use Bootstrap with Vue i would highly recommend using [Bootstrap-Vue](https://bootstrap-vue.js.org/). They've remade all component to be vue compatible so you don't need to use jQuery. They also have custom components like a date-picker and a datatable. – Hiws Mar 07 '20 at 18:58
  • I will definitely give Bootstrap-Vue a try. I am building my project with Laravel 6. Do I need to setup Laravel differently to use Bootstrap-Vue? – dottedquad Mar 07 '20 at 19:20
  • 1
    I haven't used Laravel, so I'm not 100% sure. However, it should be a simple case of importing and adding it to your Vue instance. You might need to do some extra setup if you choose to use `SCSS` so that it compiles correctly. – Hiws Mar 07 '20 at 19:31
  • 1
    You might find an answer in this thread https://stackoverflow.com/questions/55915329/how-to-include-bootstrap-vue-in-laravel – Hiws Mar 07 '20 at 19:32

1 Answers1

3

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>
Hiws
  • 8,230
  • 1
  • 19
  • 36
  • I stepped away for a moment to think about what I should be doing. I realized that I should have nested the product object inside of the category object. I then attempted to refactor my code to see if I could figure this out, but you beat me to the finish line :-) – dottedquad Mar 07 '20 at 19:18