I had a project with repetitive HTML, JS code blocks in which should be ten fields (A, B, C...) with certain functionality. Three fields are already implemented, but I don't want to copy and paste code blocks. I was able to solve some of the repetitions, but majority of that code I wasn't able to fix. I am not sure what is even possible to do in that regard according to app logic. Otherwise, the app works well. I would appreciate any help, or anything to point me out to the right direction. Please refer to full working code example: https://codesandbox.io/s/black-sound-rseqb.
Task requirements:
Create a Vue.js application with two pages.
First page should be on url '/'
- On load this page should show 10 fields marked as A, B, C, D .... with initial value 3.
- After page load, every 2 seconds all field values should be changed randomly. Change is randomly calculated as a number between 1 and 2 (1.45, 1.05...), with a separate random sign (-, +).
- When adding the change to the previous value you should show an arrow pointing up or down, depending on the change sign (arrow down is for -, arrow up is for +).
- Under each field there should be a toggle button to disable/enable the change on that field.
Second page should be on url '/statistics'
- This page should show change statistics for all 10 fields.
- Chart should show value changes in time.
When going from '/' to '/statistics' all the changing should be paused, and on returning back it should be resumed.
You can choose any libraries you want
How can DRY principles be applied to this code?
TableFields.vue
<template>
<div>
<div class="wrapper" v-for="(field, index) in fields" :key="index">
<table>
<tr>
<th>{{ field }}</th>
<td class="sign">{{ randomSign[index] }}</td>
<td>{{ initialValues[index].toFixed(2) }}</td>
<td v-show="randomSign[index] == '+'">⬆</td>
<td v-show="randomSign[index] == '-'">⬇</td>
</tr>
</table>
<button
@click="toggleInterval(field)"
v-if="field === 'A'"
:class="[startStopA ? 'button-start' : 'button-stop']"
>
<span v-show="startStopA">Stop</span>
<span v-show="!startStopA">Start</span>
</button>
<button
@click="toggleInterval(field)"
v-if="field === 'B'"
:class="[startStopB ? 'button-start' : 'button-stop']"
>
<span v-show="startStopB">Stop</span>
<span v-show="!startStopB">Start</span>
</button>
<button
@click="toggleInterval(field)"
v-if="field === 'C'"
:class="[startStopC ? 'button-start' : 'button-stop']"
>
<span v-show="startStopC">Stop</span>
<span v-show="!startStopC">Start</span>
</button>
</div>
</div>
</template>
<script>
import store from "../store";
export default {
name: "TableFields",
data() {
return {
timer: [undefined, undefined, undefined],
fields: ["A", "B", "C"],
startStopA: true,
startStopB: true,
startStopC: true,
initialValueA: 3,
initialValueB: 3,
initialValueC: 3,
randomNumbers: [],
randomSign: ["+", "+", "+"],
signs: ["+", "-"],
changes: store.changes
};
},
computed: {
initialValues() {
const array = [
this.initialValueA,
this.initialValueB,
this.initialValueC
];
return array;
}
},
methods: {
firstObjects() {
// creates first objects A, B, C...
for (let i = 0; i < this.fields.length; i++) {
const date = new Date();
const obj = {};
obj.field = this.fields[i];
obj.value = Number((Math.random() * 1 + 1).toFixed(2));
obj.indicator = this.signs[
Math.floor(Math.random() * this.signs.length)
];
obj.time = date.toLocaleTimeString();
this.changes.push({ ...obj });
this.$emit("update:changes", [...this.changes]);
}
},
replaceNumbersArray() {
// replace random A, B, C... numbers at time interval
const A = Number((Math.random() * 1 + 1).toFixed(2)); // first number A
const B = Number((Math.random() * 1 + 1).toFixed(2)); // first number B
const C = Number((Math.random() * 1 + 1).toFixed(2)); // first number C
this.randomNumbers.splice(0, 3, A, B, C);
},
toggleInterval(field) {
// button toggle
if (field === "A") {
this.startStopA = !this.startStopA;
if (this.startStopA) {
this.timer[0] = setInterval(() => {
this.calculations("A");
}, 2000);
} else {
clearInterval(this.timer[0]);
}
}
if (field === "B") {
this.startStopB = !this.startStopB;
if (this.startStopB) {
this.timer[1] = setInterval(() => {
this.calculations("B");
}, 2000);
} else {
clearInterval(this.timer[1]);
}
}
if (field === "C") {
this.startStopC = !this.startStopC;
if (this.startStopC) {
this.timer[2] = setInterval(() => {
this.calculations("C");
}, 2000);
} else {
clearInterval(this.timer[2]);
}
}
},
calculations(field) {
this.fields.forEach((value, index) => {
if (field === value) {
this.randomSign[index] = this.signs[
Math.floor(Math.random() * this.signs.length)
];
const date = new Date();
const newChange = [{}, {}, {}];
newChange[index].field = field;
newChange[index].indicator = this.randomSign[index];
newChange[index].value = this.randomNumbers[index];
newChange[index].time = date.toLocaleTimeString();
this.changes[index].push(newChange[index]);
this.$emit("update:changes[index]", [...this.changes[index]]);
}
});
if (field === "A") {
this.randomSign[0] === "+"
? (this.initialValueA += this.randomNumbers[0])
: (this.initialValueA -= this.randomNumbers[0]);
}
if (field === "B") {
this.randomSign[1] === "+"
? (this.initialValueB += this.randomNumbers[1])
: (this.initialValueB -= this.randomNumbers[1]);
}
if (field === "C") {
this.randomSign[2] === "+"
? (this.initialValueC += this.randomNumbers[2])
: (this.initialValueC -= this.randomNumbers[2]);
}
}
},
beforeUpdate() {
const array = [this.startStopA, this.startStopB, this.startStopC];
array.forEach((value, index) => {
if (!value) {
clearInterval(this.timer[index]);
}
});
},
mounted() {
if (this.changes === []) {
this.firstObjects();
}
setInterval(this.replaceNumbersArray, 2000);
this.initialValueA = this.$root.initialValueA || 3;
this.initialValueB = this.$root.initialValueB || 3;
this.initialValueC = this.$root.initialValueC || 3;
this.fields.forEach((value, index) => {
this.timer[index] = setInterval(() => {
this.calculations(value);
}, 2000);
});
this.startStopA = !this.$root.startStopA || !this.startStopA;
this.startStopB = !this.$root.startStopB || !this.startStopB;
this.startStopC = !this.$root.startStopC || !this.startStopC;
},
beforeDestroy() {
this.$root.initialValueA = this.initialValueA;
this.$root.initialValueB = this.initialValueB;
this.$root.initialValueC = this.initialValueC;
this.$root.startStopA = !this.startStopA;
this.$root.startStopB = !this.startStopB;
this.$root.startStopC = !this.startStopC;
this.timer.forEach(value => {
clearInterval(value);
});
}
};
</script>
Statistics.vue
<template>
<div class="statistics">
<table v-for="(field, index) in fields" :key="index">
<tr>
<th>Field</th>
<th>Value</th>
<th>+/-</th>
<th>Time</th>
</tr>
<tr v-for="(change, index) in changes[index]" :key="index">
<td>{{ change.field }}</td>
<td>{{ change.value }}</td>
<td v-show="change.indicator == '+'">⬆</td>
<td v-show="change.indicator == '-'">⬇</td>
<td>{{ change.time }}</td>
</tr>
</table>
</div>
</template>
<script>
import store from "../store";
export default {
name: "Statistics",
data() {
return {
fields: ["A", "B", "C"],
changes: store.changes
};
},
mounted() {
}
};
</script>
App.vue
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/statistics">Statistics</router-link>
</div>
<router-view :changes.sync="changes" />
</div>
</template>
<script>
import store from "./store";
export default {
name: "App",
data() {
return {
changes: store.changes
};
},
mounted() {
for (let i = 0; i < 3; i++) {
this.changes.push([]);
}
}
};
</script>
store.js
import Vue from "vue";
export default Vue.observable({
changes: []
});