I have an admin panel route with 2 nested child routes that render 2 views – AdminBrands
renders brands and allow the admin to see and delete brands, and AdminCategories
where the admin can see and delete categories. Now the problem is that those 2 views are super similar to each other, but at the same time they have got some differences, so I’m not exactly sure how to DRY them up or even if I should.
The template is mostly the same, with just a couple of small differences. The script part is literally identical, except that the data and functions have different names – getCategories()
and getBrands()
for example. Should I try creating some all-mighty view that changes depending on the current route, or stick to having more than 1 components? I’ve given 2 views as my example, but in my project, I actually have 5 views that are extremely similar, so creating a single view would allow me to remove 4 of them which will make the folder structure a lot cleaner.
{ path: "/admin", component: AdminPanel, children: [ { path: "categories", name: "adminCategories", component: AdminCategories }, { path: "brands", name: "adminBrands", component: AdminBrands } ] }
AdminBrands view:
<template lang="html"> <div class="admin-brands"> <div class="column-title"> <p>Марки</p> <div class="create-button" @click="goToCreateBrand()">Добави</div> </div> <div class="brands"> <div class="brand" v-for="brand in brands"> <p>{{ brand.name }}</p> <div class="btn btn-danger" @click="deleteBrand(brand.id)">Delete</div> </div> </div> <Pagination :currentPage="currentPage" :totalPages="totalPages" @setCurrentPage="setCurrentPage"></Pagination> </div> </template> <script> import axios from 'axios' import router from "../../router" import Pagination from "../Pagination" export default { components: { Pagination }, data() { return { brands: null, currentPage: 1, totalItems: null, totalPages: null } }, methods: { async getBrands(){ try { let response = await axios.get("/brands?page=" + this.currentPage) this.brands = response.data.brands this.totalItems = response.data.totalItems this.totalPages = response.data.totalPages } catch (e) { console.log(e) } }, async deleteBrand(brandId){ let response = await axios.post('/deleteBrand', { brandId }) console.log(response) }, setCurrentPage(page){ this.currentPage = page }, goToCreateBrand(){ router.push({ name: "createBrand" }) } }, mounted(){ this.getBrands() } } </script> <style lang="css" scoped> .column-title { margin-bottom: 0px; } .brand { display: flex; align-items: center; border-bottom: 1px solid #dddddd; padding: 20px 0; font-size: 15px; } .btn { margin-left: auto; width: 100px; } </style>
AdminCategories view:
<template lang="html"> <div class="admin-categories"> <div class="column-title"> <p>Категории</p> <div class="create-button" @click="goToCreateCategory()">Добави</div> </div> <div class="categories"> <div class="category" v-for="category in categories"> <div class="category-image-container"> <img class='category-image' :src="category.image" :alt="category.name"> </div> <p>{{ category.name }}</p> <div class="btn btn-danger" @click="deleteCategory(category.id)">Delete</div> </div> </div> <Pagination :currentPage="currentPage" :totalPages="totalPages" @setCurrentPage="setCurrentPage"></Pagination> </div> </template> <script> import axios from 'axios' import router from "../../router" import Pagination from "../Pagination" export default { components: { Pagination }, data() { return { categories: null, currentPage: 1, totalItems: null, totalPages: null } }, methods: { async getCategories(){ let response = await axios.get("/categories?page=" + this.currentPage) this.categories = response.data.categories this.totalItems = response.data.totalItems this.totalPages = response.data.totalPages }, async deleteCategory(categoryId){ let response = await axios.post("/deleteCategory", { categoryId }) console.log(response) }, setCurrentPage(page){ this.currentPage = page }, goToCreateCategory(){ router.push({ name: "createCategory" }) } }, watch: { currentPage(){ this.getCategories() } }, mounted(){ this.getCategories() } } </script> <style lang="css" scoped> .column-title { margin-bottom: 0px; } .category { display: flex; align-items: center; border-bottom: 1px solid #dddddd; padding: 20px 0; font-size: 15px; } .category-image-container { display: flex; justify-content: center; align-items: center; width: 100px; height: 100px; margin-right: 15px; } .category-image { display: block; max-height: 100%; max-width: 100%; } .btn { margin-left: auto; width: 100px; } </style>
Advertisement
Answer
For now, the best solution is to abstract completely identical (common) functions with Vue Mixins such as setCurrentPage
and most of your data points currentPage
, totalItems
and totalPages
. Furthermore, you can abstract very similar functions, such as getCategories
/getBrands
and deleteCategory
/deleteBrand
. Here it’s best if you abstract each pair into one function, taking an input whether it’s about categories or brands, and then working with them.