Skip to content

03. Vue Components

Now we will move to create components for CRUD operations of Category, Product, Order etc.

We will create a common form template for taking the data from the user and submitting to the backend.

Parent Template

We will create a common form template with simple validation and UI.

js filename=static/components/FormCompo.js

<template>
  <div class="row justify-content-center m-3">
    <div class="card bg-light" style="width: 18rem;">
      <div class="card-body">
        <div class="d-flex justify-content-end">
          <!-- Cross button to close the card -->
          <button type="button" class="btn-close" aria-label="Close" @click="home"></button>
        </div>
        <slot name="form"></slot>
      </div>
    </div>
  </div>
</template>

In the above template you can see the <slot name="form"></slot> tag. This is the way to create placeholder which will get overridden by child component or the component which will inherit the parent component.

Template Inheritance

Now let's create the components for CRUD operations.

Create Category

js filename=static/components/CreateCatCompo.js

<template>
  <FormCompo>
    <template v-slot:form>
      <h5 class="card-title">Add Category</h5>
      <form @submit.prevent="addcategory">
        <div class="mb-3">
          <label for="name" class="form-label">Category Name</label>
          <input type="text" class="form-control" v-model="name" required>
          <div v-if="message" class="alert alert-warning">
            {{ message }}
          </div>
        </div>
        <button type="submit" class="btn btn-outline-primary">Add</button>
      </form>
    </template>
  </FormCompo>//component use
</template>

<script type="module">
export default {
  name: 'CreateCatCompo',
  components: {
    FormCompo //component binding
  },
  data() {
    return {
      name: '',
      message: ''
    }
  },
  methods: {
    home() {
      home(this.$store, this.$route, this.$router);
    },
    async addcategory() {
      /* write your code here */
    }
  }
}
</script>

You can see that to use parent template in the children component you need to bind it with the children component. And you use the component name as html tag.

Update Category

Similary you can create update category component.

js filename=static/components/EditCatCompo.js

<template>
  <FormCompo>
    <template v-slot:form>
      <h5 class="card-title">Add Category</h5>
      <form @submit.prevent="updatecategory">
        <div class="mb-3">
          <label for="name" class="form-label">Category Name</label>
          <input type="text" class="form-control" v-model="name" required>
          <div v-if="message" class="alert alert-warning">
            {{ message }}
          </div>
        </div>
        <div class="d-flex">
          <button type="submit" class="btn btn-outline-primary me-5">Update</button>
          <a class="btn btn-outline-danger" @click="deletecategory">Delete</a>
        </div>
      </form>
    </template>
  </FormCompo>
</template>

<script>
export default {
  name: 'EditCatCompo',
  components: {
    FormCompo
  },
  data() {
    return {
      name: '',
      message: ''
    }
  },
  methods: {
    // write your code here
  },
  mounted() {
    this.fetchcategory()
  }
}
</script>

Now we have the CRUD template for Category. Next we will focus on the CRUD for Product.

Create Product

js filename=static/components/CreateProCompo.js

<template>
  <FormCompo>
    <template v-slot:form>
      <h5 class="card-title">Add Product</h5>
      <form @submit.prevent="addProduct" enctype="multipart/form-data">
        <label class="form-label" for="name">Product Name:</label>
        <input class="form-control" v-model="product.name" type="text" id="name" name="name" required>
        <br>

        <label class="form-label" for="quantity">Quantity:</label>
        <input class="form-control" v-model="product.quantity" type="number" id="quantity" name="quantity" required>
        <br>

        <label class="form-label" for="manufacture">Manufacture Date:</label>
        <input class="form-control" v-model="product.manufacture" type="date" id="manufacture" name="manufacture"
          required>
        <br>

        <label class="form-label" for="expiry">Expiry Date:</label>
        <input class="form-control" v-model="product.expiry" type="date" id="expiry" name="expiry" required>
        <br>

        <label class="form-label" for="rpu">Rate Per Unit:</label>
        <input class="form-control" v-model="product.rpu" type="number" id="rpu" name="rpu" step="0.01" required>
        <br>

        <label class="form-label" for="description">description:</label>
        <textarea class="form-control" v-model="product.description" type="text" id="description" name="description"
          required></textarea>
        <br>
        <label class="form-label" for="Select Category">Select Category:</label>
        <select class="form-select" name="Select Category" id="Select Category" v-model="product.category_id" required>
          <option v-for="category in this.$store.state.categories" :key="category.id" :value="category.id">
            {{ category.name }}</option>
        </select>
        <label class="form-label" for="unit">Unit:</label>
        <select class="form-select" name="Select Unit" v-model="product.unit" required>
          <option value="l">l</option>
          <option value="ml">ml</option>
          <option value="g">g</option>
          <option value="kg">kg</option>
          <option value="m">m</option>
          <option value="cm">cm</option>
          <option value="inch">inch</option>
          <option value="piece">piece</option>
          <option value="dozen">dozen</option>
        </select>
        <br>
        <label class="form-label" for="image">Image:</label>
        <input class="form-control" type="file" id="image" @change="handleFileUpload" accept="image/*" required>
        <br>
        <input type="submit" class="btn btn-outline-primary" value="Add Product">
      </form>
    </template>
  </FormCompo>
</template>

<script type="module">
export default {
  name: 'CreateProCompo',
  components: {
    FormCompo
  },
  data() {
    return {
      product: {
        name: '',
        quantity: 0,
        manufacture: '',
        expiry: '',
        rpu: 0,
        unit: '',
        description: '',
        image: null,
        category_id: ''
      }
    }
  },
  methods: {
    // write your code here
    handleFileUpload(event) {
      this.product.image = event.target.files[0];
    },
  }
}
</script>

Already we have worked with sending text data in the previous Category form. Now in this Product form we are dealing with file type. To upload a file and submit it to backend we need to use the enctype="multipart/form-data" attribute in the form tag.



handleFileUpload(event) {
  this.product.image = event.target.files[0];
},

is listening to the click event and on click it will take the first file from the input field and store it in the product.image variable, that is why we are using index '0'.

Update Product

js filename=static/components/EditProCompo.js

<template>
  <FormCompo>
    <template v-slot:form>
      <h5 class="card-title">Update Product</h5>
      <form @submit.prevent="updateproduct" enctype="multipart/form-data">
        <label class="form-label" for="name">Product Name:</label>
        <input class="form-control" v-model="product.name" type="text" id="name" name="name" required>
        <br>

        <label class="form-label" for="quantity">Quantity:</label>
        <input class="form-control" v-model="product.quantity" type="number" id="quantity" name="quantity" required>
        <br>

        <label class="form-label" for="manufacture">Manufacture Date:</label>
        <input class="form-control" v-model="product.manufacture" type="date" id="manufacture" name="manufacture"
          required>
        <br>

        <label class="form-label" for="expiry">Expiry Date:</label>
        <input class="form-control" v-model="product.expiry" type="date" id="expiry" name="expiry" required>
        <br>

        <label class="form-label" for="description">description:</label>
        <textarea class="form-control" v-model="product.description" type="text" id="description" name="description"
          required></textarea>
        <br>

        <label class="form-label" for="rpu">Rate Per Unit:</label>
        <input class="form-control" v-model="product.rpu" type="number" id="rpu" name="rpu" step="0.01" required>
        <br>
        <label class="form-label" for="Select Category">Select Category:</label>
        <select class="form-select" name="Select Category" v-model="product.category_id" required>
          <option v-for="category in this.$store.state.categories" :key="category.id" :value="category.id">
            {{ category.name }}</option>
        </select>
        <label class="form-label" for="unit">Unit:</label>
        <select class="form-select" name="Select Unit" v-model="product.unit" required>
          <option value="l">l</option>
          <option value="ml">ml</option>
          <option value="g">g</option>
          <option value="kg">kg</option>
          <option value="m">m</option>
          <option value="cm">cm</option>
          <option value="inch">inch</option>
          <option value="piece">piece</option>
          <option value="dozen">dozen</option>
        </select>
        <br>

        <label class="form-label" for="image">Image:</label>
        <input class="form-control" type="file" id="image" @change="handleFileUpload" accept="image/*" required>
        <br>
        <div class="d-flex">
          <button type="submit" class="btn btn-outline-primary me-5">Update</button>
          <a class="btn btn-outline-danger" @click="deleteproduct">Delete</a>
        </div>
      </form>
    </template>
  </FormCompo>
</template>

<script type="module">
export default {
  name: 'EditProCompo',
  components: {
    FormCompo
  },
  data() {
    return {
      product: {
        id: '',
        quantity: 0,
        name: '',
        manufacture: '',
        description: '',
        expiry: '',
        rpu: 0,
        unit: '',
        image: null,
        category_id: ''
      }
    }
  },
  methods: {
    handleFileUpload(event) {
      this.product.image = event.target.files[0];
    }
  },
  mounted() {
    // write your code here
  }
}
</script>

So far whatever components we have created let's add them to our routers.

js filename=static/router/index.js

import { createRouter, createWebHistory } from "vue-router";

const routes = [
  {
    path: "/",
    name: "LandPage",
    component: () => import("../views/LandPage.vue"),
  },
  {
    path: "/login",
    name: "Login",
    component: () => import("../views/LoginPage.vue"),
  },
  {
    path: "/register",
    name: "Register",
    component: () => import("../views/RegisterPage.vue"),
  },
  // Catch-all route for undefined paths
  {
    path: "/:pathMatch(.*)*", // Use '*' for Vue Router 3
    name: "NotFound",
    component: () => import("../components/NotFound.vue"),
  },
  {
    path: "/admin",
    component: () => import("../views/AdminDash.vue"),
    children: [
      {
        path: "/admin",
        component: () => import("../components/ProductCompo.vue"),
      },
      {
        path: "/admin/cat/create",
        component: () => import("../components/CreateCatCompo.vue"),
      },
      {
        path: "/admin/cat/edit/:id",
        component: () => import("../components/EditCatCompo.vue"),
      },
      {
        path: "/admin/pro/create",
        component: () => import("../components/CreateProCompo.vue"),
      },
      {
        path: "/admin/pro/edit/:id",
        component: () => import("../components/EditProCompo.vue"),
      }
    ],
  },
  {
    path: "/manager",
    component: () => import("../views/ManagerDash.vue"),
    children: [
      {
        path: "/manager",
        component: () => import("../components/ProductCompo.vue"),
      },
      {
        path: "/manager/cat/create",
        component: () => import("../components/CreateCatCompo.vue"),
      },
      {
        path: "/manager/cat/edit/:id",
        component: () => import("../components/EditCatCompo.vue"),
      },
      {
        path: "/manager/pro/create",
        component: () => import("../components/CreateProCompo.vue"),
      },
      {
        path: "/manager/pro/edit/:id",
        component: () => import("../components/EditProCompo.vue"),
      }
    ],
  },
  {
    path: "/user",
    component: () => import("../views/UserDash.vue"),
  },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;

Now next we will add the arrays to store the data into the store index.js

js filename=static/store/index.js

const store = createStore({ state: { products: [], categories: [] }, getters: { // write your code here }, mutations: { setProducts: (state, products) => { state.products = products; }, setCategories: (state, categories) => { state.categories = categories; }, addProduct: (state, newProduct) => { state.products.push(newProduct); }, addCat: (state, newCategory) => { state.categories.push(newCategory); }, updateCategory: (state, updatedCategory) => { const index = state.categories.findIndex( (p) => p.id === updatedCategory.id ); if (index !== -1) { state.categories.splice(index, 1, updatedCategory); } },
updateProduct: (state, updatedProduct) => { const index = state.products.findIndex((p) => p.id === updatedProduct.id); if (index !== -1) { state.products.splice(index, 1, updatedProduct); } }, deleteCategory: (state, categoryId) => { state.categories = state.categories.filter((p) => p.id !== categoryId); }, deleteProduct: (state, productId) => { state.products = state.products.filter((p) => p.id !== productId); } }, actions: { // write your code here }, });

export default store;




You can see that I have also set the mutations for the product and category.

## Testing it out

Now let's test it out.

Run the app.

npm run dev




After that, you can open your browser and navigate to [http://localhost:5173](http://localhost:5173) to see your fresh app.

Let's commit the change using the following command.

git add . git commit -m "Created components and added store"

```

Continue to start creating backend...