I have this working code which shows the progress bar based on different states ("not-started", "progressing" and "done"). Each state will have its own class.
I notice it is not Don't Repeat Yourself (DRY) enough but not sure how I can refactor it. Appreciate your code review.
new Vue({
el: "#page",
data: {
currentStepNumber:10,
totalEformStepNumber: 9,
totalVerificationStepNumber: 2,
totalReviewStepNumber: 1,
eFormProgress: "not-started",
verificationProgress: "not-started",
reviewProgress: "not-started",
verificationDone: false,
encourageText: "Let's Start"
},
computed: {
checkEFormProgress() {
if (this.eFormProgress === "not-started") {
return "step-indicator-not-started";
} else if (this.eFormProgress === "progressing") {
return "step-indicator-progressing";
} else if (this.eFormProgress === "done") {
return "step-indicator-done";
}
},
checkVerificationProgress() {
if (this.verificationProgress === "not-started") {
return "step-indicator-not-started";
} else if (this.verificationProgress === "progressing") {
return "step-indicator-progressing";
} else if (this.verificationProgress === "done") {
return "step-indicator-done";
}
},
checkReviewProgress() {
if (this.reviewProgress === "not-started") {
return "step-indicator-not-started";
} else if (this.reviewProgress === "progressing") {
return "step-indicator-progressing";
} else if (this.reviewProgress === "done") {
return "step-indicator-done";
}
},
totalStepNumber() {
return (
this.totalEformStepNumber +
this.totalVerificationStepNumber +
this.totalReviewStepNumber
);
},
eformCurrentStepNumber() {
if (this.currentStepNumber === 0) {
this.eFormProgress = "not-started";
} else if (this.currentStepNumber < this.totalEformStepNumber) {
this.eFormProgress = "progressing";
} else if (this.currentStepNumber >= this.totalEformStepNumber) {
this.eFormProgress = "done";
}
return (this.currentStepNumber / this.totalEformStepNumber) * 100;
},
verifcationCurrentStepNumber() {
debugger;
if (this.currentStepNumber >= this.totalEformStepNumber) {
if (this.currentStepNumber < this.totalEformStepNumber) {
this.verificationProgress = "not-started";
} else if (this.currentStepNumber > this.totalEformStepNumber && this.currentStepNumber < this.totalStepNumber) {
this.verificationProgress = "progressing";
} else if (this.currentStepNumber === this.totalStepNumber) {
this.verificationProgress = "done";
this.reviewProgress = "done";
}
return (
((this.currentStepNumber - this.totalEformStepNumber) /
this.totalVerificationStepNumber) *
100
);
} else {
return 0;
}
},
eformProgressWidth() {
return this.eformCurrentStepNumber > 100
? { width: "100%" }
: { width: this.eformCurrentStepNumber + "%" };
},
verificationProgressWidth() {
return this.verifcationCurrentStepNumber >= 100
? { width: "100%" }
: { width: this.verifcationCurrentStepNumber + "%" };
}
},
methods: {},
created() {}
});
@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
p {
margin: 0;
}
#page {
font-family: Roboto;
width: 100%;
background: #f8f9fa;
display: flex;
flex-direction: column;
align-items: center;
}
.container {
width: 70%;
text-align: center;
}
.progress-bar {
display: flex;
flex-direction: row;
align-items: flex-start;
width: 100%;
}
.point {
width: 38px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
color: #28a745;
}
.step-indicator-not-started {
width: 15px;
height: 15px;
border-radius: 100px;
background: #ecedee;
box-sizing: border-box;
border: none;
}
.step-indicator-progressing {
width: 15px;
height: 15px;
border-radius: 100px;
background: #ecedee;
box-sizing: border-box;
border: 4px solid #28a745;
}
.step-indicator-done {
width: 15px;
height: 15px;
border-radius: 100px;
background: #28a745;
box-sizing: border-box;
border: 4px solid #28a745;
}
.bar {
width: 100%;
height: 16px;
display: grid;
align-items: center;
}
.bar-eform {
width: 80%;
}
.bar-verification {
width: 20%;
}
.bar-bg {
height: 4px;
background-color: #ecedee;
position: relative;
}
.bar-arrow {
position: absolute;
top: 0;
left: 0;
height: 4px;
background-color: #28a745;
}
.page-indicator {
color: #28a745;
font-size: 14px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<div id="page">
<div class="container">
<h3>{{encourageText}}</h3>
<div class="progress-bar">
<div class="point">
<div :class="checkEFormProgress">
</div>
<p><strong>EForm</strong></p>
</div>
<div class="bar bar-eform">
<div class="bar-bg">
<div class="bar-arrow" v-bind:style="eformProgressWidth"></div>
</div>
</div>
<div class="point">
<div :class="checkVerificationProgress">
</div>
<p><strong>Verification</strong></p>
</div>
<div class="bar bar-verification">
<div class="bar-bg">
<div class="bar-arrow" v-bind:style="verificationProgressWidth"></div>
</div>
</div>
<div class="point">
<div :class="checkReviewProgress">
</div>
<p><strong>Review</strong></p>
</div>
</div>
</div>
<div class="page-indicator">
<strong>{{currentStepNumber}}/{{totalStepNumber}}</strong>
</div>
</div>
Version 2, I have done some refactoring as below.
const stateProgress = {
NOTSTARTED: "not-started",
PROGRESSING: "progressing",
DONE: "done"
};
const stateProgressClassName = {
NOTSTARTED: "step-indicator-not-started",
PROGRESSING: "step-indicator-progressing",
DONE: "step-indicator-done"
};
new Vue({
el: "#page",
data: {
currentStepNumber: 2,
totalEformStepNumber: 9,
totalVerificationStepNumber: 2,
totalReviewStepNumber: 1,
eFormProgress: stateProgress.NOTSTARTED,
verificationProgress: stateProgress.NOTSTARTED,
reviewProgress: stateProgress.NOTSTARTED,
encourageText: "Let's Start"
},
computed: {
checkEFormProgress() {
return this.getProgressClassName(this.eFormProgress);
},
checkVerificationProgress() {
return this.getProgressClassName(this.verificationProgress);
},
checkReviewProgress() {
return this.getProgressClassName(this.reviewProgress);
},
totalStepNumber() {
return (
this.totalEformStepNumber +
this.totalVerificationStepNumber +
this.totalReviewStepNumber
);
},
eformCurrentStepNumber() {
if (this.currentStepNumber <= 0) {
this.eFormProgress = stateProgress.NOTSTARTED;
} else if (this.currentStepNumber < this.totalEformStepNumber) {
this.eFormProgress = stateProgress.PROGRESSING;
} else if (this.currentStepNumber >= this.totalEformStepNumber) {
this.eFormProgress = stateProgress.DONE;
}
return (this.currentStepNumber / this.totalEformStepNumber) * 100;
},
verifcationCurrentStepNumber() {
debugger;
if (this.currentStepNumber >= this.totalEformStepNumber) {
if (this.currentStepNumber < this.totalEformStepNumber) {
this.verificationProgress = stateProgress.NOTSTARTED;
} else if (
this.currentStepNumber > this.totalEformStepNumber &&
this.currentStepNumber < this.totalStepNumber
) {
this.verificationProgress = stateProgress.PROGRESSING;
} else if (this.currentStepNumber === this.totalStepNumber) {
this.verificationProgress = stateProgress.DONE;
this.reviewProgress = stateProgress.PROGRESSING;
}
return (
((this.currentStepNumber - this.totalEformStepNumber) /
this.totalVerificationStepNumber) *
100
);
} else {
return 0;
}
},
eformProgressWidth() {
return this.eformCurrentStepNumber > 100
? { width: "100%" }
: { width: this.eformCurrentStepNumber + "%" };
},
verificationProgressWidth() {
return this.verifcationCurrentStepNumber >= 100
? { width: "100%" }
: { width: this.verifcationCurrentStepNumber + "%" };
}
},
methods: {
getProgressClassName : function(progressState) {
if (progressState === stateProgress.NOTSTARTED) {
return stateProgressClassName.NOTSTARTED;
} else if (progressState === stateProgress.PROGRESSING) {
return stateProgressClassName.PROGRESSING;
} else if (progressState === stateProgress.DONE) {
return stateProgressClassName.DONE;
}
}
},
created() {}
});
@import url("https://fonts.googleapis.com/css2?family=Roboto&display=swap");
p {
margin: 0;
}
#page {
font-family: Roboto;
width: 100%;
background: #f8f9fa;
display: flex;
flex-direction: column;
align-items: center;
}
.container {
width: 70%;
text-align: center;
}
.progress-bar {
display: flex;
flex-direction: row;
align-items: flex-start;
width: 100%;
}
.point {
width: 38px;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
color: #28a745;
}
.step-indicator-not-started {
width: 15px;
height: 15px;
border-radius: 100px;
background: #ecedee;
box-sizing: border-box;
border: none;
}
.step-indicator-progressing {
width: 15px;
height: 15px;
border-radius: 100px;
background: #ecedee;
box-sizing: border-box;
border: 4px solid #28a745;
}
.step-indicator-done {
width: 15px;
height: 15px;
border-radius: 100px;
background: #28a745;
box-sizing: border-box;
border: 4px solid #28a745;
}
.bar {
width: 100%;
height: 16px;
display: grid;
align-items: center;
}
.bar-eform {
width: 80%;
}
.bar-verification {
width: 20%;
}
.bar-bg {
height: 4px;
background-color: #ecedee;
position: relative;
}
.bar-arrow {
position: absolute;
top: 0;
left: 0;
height: 4px;
background-color: #28a745;
}
.page-indicator {
color: #28a745;
font-size: 14px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="page">
<div class="container">
<h3>{{encourageText}}</h3>
<div class="progress-bar">
<div class="point">
<div :class="checkEFormProgress">
</div>
<p><strong>Account</strong></p>
</div>
<div class="bar bar-eform">
<div class="bar-bg">
<div class="bar-arrow" v-bind:style="eformProgressWidth"></div>
</div>
</div>
<div class="point">
<div :class="checkVerificationProgress">
</div>
<p><strong>Verification</strong></p>
</div>
<div class="bar bar-verification">
<div class="bar-bg">
<div class="bar-arrow" v-bind:style="verificationProgressWidth"></div>
</div>
</div>
<div class="point">
<div :class="checkReviewProgress">
</div>
<p><strong>Review</strong></p>
</div>
</div>
</div>
<div class="page-indicator">
<strong>{{currentStepNumber}}/{{totalStepNumber}}</strong>
</div>
</div>