Skip to content
Advertisement

Two of my routes are extremely similar, to the point that I copy-pasted most of the stuff. I assume I need to DRY the code but I’m not sure how

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.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement