I want to show you a little practical job where I had to improve some listing components with multiple elements in Nuxt.js (Vue.js framework) but the principle is the same in React, Angular, or other Front-end, libraries, frameworks, or native code. This can help junior and not only developers, to make better code and improve themselves, also if you can make this imperfect Vue component better, please write in comments. It would be great to share experiences and see new approaches.
On 3 January, after holiday parties, I was tired and didn’t want to work, but found an interesting recruiting project, which was finished in half. UI and code were terrible and I looked at this like challenge. After a few words, the deal was finished with the client and the job started.
First, see screenshots I have made before, and after improving code in the simple listing component.
Before change source code
After source code improving
Design.
The difference will be visible to anyone not dealing with the website layout. Even my code has a design inconsistency, like a difference in marker size, logo, or title padding, but this was fixed at the request of the client.
Now, the most interesting is the realization of code. It was one huge component with a lot of HTML, CSS, and some unnecessary js with categoryIcon computed property (different story).
Despite the fact that the year 2021 has come, many people continue to write this way, but I ask you not to do this, do not make life difficult for yourself or other developers.
<template>
<div class="row">
<div class="col-lg-12">
<div class="row job-vacancy d-none d-lg-flex">
<div class="col-lg-9">
<div class="row">
<div class="col-lg-6">
<div class="vacancy-name">{{ title }}</div>
<div class="salary">{{ salary }} ₽</div>
</div>
<div class="col-lg-6 d-flex">
<div v-if="isDelete" class="icon">
<img src="~/static/images/delete.svg" alt=""/>
</div>
<div v-else-if="location" class="icon">
<img src="~/static/images/city.svg" alt=""/>
</div>
<div v-if="isDelete" class="delete">Удаленно</div>
<div v-else-if="location" class="geo">
{{ location }}
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12 d-flex flex-wrap">
<div class="description">
<img :src="categoryIcon" alt=""/>
{{ category }}
</div>
<ul class="tags">
<li
v-for="(tag, index) in tags"
:key="index"
class="tag"
>
{{ tag.tag }}
</li>
</ul>
</div>
</div>
</div>
<div class="col-lg-3">
<div class="company-logo" v-if="logo">
<img :src="logo" alt=""/>
</div>
<div class="public-data">
<img src="~/static/images/time.svg" alt=""/>
<span>{{ $moment(dataPublished).fromNow() }}</span>
</div>
</div>
</div>
<div class="row job-vacancy-mobile d-block d-lg-none">
<div class="col-12">
<div class="vacancy-name">
{{ title }}
<img v-if="isDelete" src="/images/delete.svg" alt=""/>
</div>
</div>
<div class="col-12">
<div class="salary">{{ salary }}</div>
</div>
<div class="col-12" v-if="logo">
<div class="company-logo">
<img :src="logo" alt=""/>
</div>
</div>
<div class="col-12">
<div class="description">
<img :src="categoryIcon" alt=""/>
{{ category }}
</div>
</div>
<div class="col-12 d-flex mt-3">
<div v-if="isDelete" class="icon"></div>
<div v-else class="icon">
<img src="~/static/images/city.svg" alt=""/>
</div>
<div v-if="isDelete" class="delete">Удалено</div>
<div v-else class="geo">{{ location }}</div>
</div>
<div class="col-12">
<div class="public-data">
<img src="~/static/images/time.svg" alt=""/>
<span>{{ $moment(dataPublished).fromNow() }}</span>
</div>
</div>
<div class="col-12">
<ul class="tags">
<li
v-for="(tag, index) in tags"
:key="index"
class="tag"
>
{{ tag.tag }}
</li>
</ul>
</div>
<div class="col-12">
<button type="button" class="btn btn-info">
просмотреть вакансию
</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "JobItem",
props: {
title: {
type: String,
default: "",
},
salary: {
type: String,
default: "",
},
isDelete: {
type: Boolean,
default: false,
},
location: {
type: String,
default: "",
},
description: {
type: String,
default: "",
},
tags: {
type: Array,
default: () => {
return [];
},
},
image: {
type: String,
default: "",
},
dataPublished: {
type: String,
default: "",
},
category: {
type: String,
default: "",
},
logo: {
type: String,
default: "",
},
descriptionLogo: {
type: String,
default: "",
},
categoryId: {
type: Number,
default: "",
},
},
computed: {
categoryIcon() {
switch (this.categoryId) {
case 1:
return "/images/development.jpg";
case 2:
return "/images/web.jpg";
case 3:
return "/images/marketing.jpg";
case 4:
return "/images/support.jpg";
case 5:
return "/images/design.jpg";
case 6:
return "/images/finance.jpg";
default:
return "/images/development.jpg";
}
},
},
};
</script>
<style scoped>
.job-vacancy {
background: #ffffff;
border-radius: 5px;
width: 100%;
margin-bottom: 19px;
margin-top: 16px;
padding: 24px;
margin-left: 0;
cursor: pointer;
}
.job-vacancy:hover,
.job-vacancy-mobile:hover {
box-shadow: 0px 2px 22px rgba(27, 32, 44, 0.12);
}
.job-vacancy:active,
.job-vacancy-mobile:active {
color: #4d8294;
}
.job-vacancy-mobile {
background: #ffffff;
border-radius: 11px;
width: 100%;
margin-top: 16px;
padding: 20px;
margin-left: 0;
cursor: pointer;
}
.tags {
display: flex;
flex-wrap: wrap;
padding: 0;
list-style-type: none;
margin: 0;
margin-bottom: 18px;
}
.tag {
padding: 4px 7px;
background: #f4fafb;
border-radius: 99px;
margin-right: 9px;
font-family: Montserrat;
font-style: normal;
font-weight: 500;
font-size: 10.1124px;
line-height: 12px;
text-transform: uppercase;
color: #38758a;
margin-bottom: 5px;
cursor: pointer;
}
.tag:hover {
background: #f4fafb;
box-shadow: 0px 2px 8px rgba(20, 25, 38, 0.12);
color: #43afd1;
}
.tag:focus {
color: #274a56;
}
.vacancy-name {
font-family: Montserrat;
font-style: normal;
font-weight: 600;
font-size: 20.2247px;
line-height: 26px;
color: #15282e;
margin-bottom: 7px;
}
.salary {
font-family: Montserrat;
font-style: normal;
font-weight: 500;
font-size: 16.1798px;
line-height: 20px;
color: #313435;
margin-bottom: 25px;
}
.geo {
font-family: Montserrat;
font-style: normal;
font-weight: normal;
font-size: 13px;
line-height: 20px;
color: #313435;
margin-left: 6px;
padding-top: 5px;
}
.job-vacancy-mobile .geo {
padding: 0;
}
.company-logo img {
background: #f3f7f9;
border-radius: 5px;
text-align: center;
padding: 15px;
max-width: 200px;
width: 100%;
}
.job-vacancy-mobile .geo {
margin-top: 2px;
}
.job-vacancy-mobile .tags {
margin-top: 26px;
}
.public-data {
font-family: Montserrat;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 20px;
text-align: right;
color: #787878;
margin-top: 15px;
}
.public-data img {
margin-right: 15px;
}
.job-vacancy-mobile .public-data {
text-align-last: left;
}
.job-vacancy-mobile .public-data img {
margin-right: 11px;
}
.delete {
font-family: Montserrat;
font-style: normal;
font-weight: 700;
font-size: 12px;
line-height: 15px;
color: #15282e;
margin-left: 8px;
padding-top: 7px;
text-transform: uppercase;
}
@media (max-width: 768px) {
.delete {
display: none;
}
}
.description {
font-family: Montserrat;
font-style: normal;
font-weight: 500;
font-size: 10px;
line-height: 12px;
text-transform: uppercase;
color: #38758a;
margin-right: 30px;
}
.description img {
margin-right: 4px;
width: 20px;
height: 20px;
}
.job-vacancy-mobile .description {
margin-top: 22px;
}
.job-vacancy-mobile .btn-info {
font-family: Montserrat;
font-style: normal;
font-weight: 600;
font-size: 10px;
line-height: 12px;
text-align: center;
text-transform: uppercase;
color: #ffffff;
padding: 12px 43px;
width: 100%;
}
</style>
Compare the code above it with the component I improved. Yes, this is not JS, PHP, or other languages, but 380 lines VS 172 after a few hours of coding, think is a good result.
<template>
<NuxtLink
:to="{
name: 'jobs-id',
params: { id: job.id }
}"
class="container-fluid job-item d-inline-block w-100 bg-color-white border-radius-s cursor-pointer"
>
<div class="row">
<div class="col-md-9 pr-md-3">
<div class="d-flex w-100 h-100">
<div
class="job-item__left-container d-flex flex-wrap flex-md-nowrap flex-md-column w-100 h-100"
>
<div
class="mb-2 d-flex align-items-start justify-content-between w-100"
>
<div
class="job-item__title montserrat semi-bold font-size-20 color-light-black d-inline-block pr-2 pr-md-0"
>
{{ job.name }}
</div>
<Location
v-if="job.is_remote"
size="small"
:data="{
isRemote: true
}"
is-job-item
/>
</div>
<div
class="job-item__salary w-100 montserrat medium color-light-black font-size-16 mb-3"
>
from {{ job.salary_min }} to {{ job.salary_max }} $
</div>
<div
v-if="job.logo_url"
class="d-md-none job-item__company-logo--mobile mr-auto p-2 mb-3 text-center border-radius-s bg-color-bg-gray"
>
<img
:src="job.logo_url"
:alt="job.company_name"
:title="job.company_name"
/>
</div>
<div class="mt-auto">
<div class="d-flex flex-wrap align-items-center">
<Location
v-if="!job.is_remote"
size="small"
:data="{
location: job.location
}"
class="mr-4 mb-3 mb-md-0"
/>
<CompanyName
v-if="job.company_name"
size="small"
>
{{ job.company_name }}
</CompanyName>
</div>
<div
class="d-flex flex-wrap align-items-center mt-4"
>
<CategoryLabel
:category="job.category"
size="small"
class="mb-3 mb-md-0"
/>
<Tags :tags="job.tags" size="small" />
</div>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div
v-if="job.logo_url"
class="d-none d-md-block mb-3 job-item__company-logo p-3 text-center border-radius-s bg-color-bg-gray"
>
<img
:src="job.logo_url"
:alt="job.company_name"
:title="job.company_name"
/>
</div>
<div
class="d-flex align-items-center color-text-gray montserrat regular font-size-12 mt-3 mt-md-0"
>
<img
src="~/static/images/time.svg"
alt=""
class="mr-3 ml-md-auto"
/>
<span>{{ $moment(job.time_created).fromNow() }}</span>
</div>
</div>
<BaseButton class="d-md-none mt-4">
See job
</BaseButton>
</div>
</NuxtLink>
</template>
<script>
import CategoryLabel from "../CategoryLabel";
import Tags from "../Tags";
import CompanyName from "../CompanyName";
import Location from "../Location";
import BaseButton from "../BaseButton";
export default {
name: "JobItem",
components: { BaseButton, Location, CompanyName, Tags, CategoryLabel },
props: {
job: {
type: Object,
default: () => {
return {};
}
}
}
};
</script>
<style lang="scss">
.job-item {
margin-bottom: rem(19);
margin-top: rem(16);
padding: rem(24) rem(26);
transition: box-shadow 0.3s ease;
&__title {
transition: color 0.3s ease;
}
&:hover,
&:active {
box-shadow: 0px rem(2) rem(22) rgba(27, 32, 44, 0.12);
}
&:hover {
.job-item__title {
color: $blue;
}
}
&:active {
.job-item__title {
color: $gray-blue;
}
}
&__left-container {
min-height: rem(130);
}
&__company-logo {
img {
max-height: rem(80);
}
&--mobile {
img {
max-height: rem(60);
}
}
}
}
</style>
To improve code, I just created reusable components, used SCSS instead of just CSS, added methodology BEM, and bootstrap utility classes. You can use some template engines like a pug or other CSS methodologies like object-oriented, create custom CSS classes, and decide to put data in components locally or store to state manager. It depends on how you and your team like to deal with it. But this is a minimum that must be done to create some normal website, and it does not matter which library(Framework) are we using, Vue (Quasar, Nuxtjs), React (Next, Gatsby), or any other.
Top comments (0)