import './style.css';
interface Scoreable {
readonly totalScore: number;
render(): void;
}
interface Foodable {
element: HTMLDivElement;
clickEventHandler(): void;
}
interface Foodsable {
elements: NodeListOf<HTMLDivElement>;
readonly activeElements: HTMLDivElement[];
readonly activeElementsScore: number[];
}
class Score implements Scoreable {
private static instance: Score;
get totalScore() {
const foods = Foods.getInstance();
return foods.activeElementsScore.reduce((total, score) => total + score, 0);
}
render() {
document.querySelector('.score__number')!.textContent = String(
this.totalScore
);
}
private constructor() {}
static getInstance() {
if (!Score.instance) {
Score.instance = new Score();
}
return Score.instance;
}
}
class Food implements Foodable {
constructor(public element: HTMLDivElement) {
// bindでthisを明示的に指し示す
element.addEventListener('click', this.clickEventHandler.bind(this));
}
clickEventHandler() {
this.element.classList.toggle('food--active');
const score = Score.getInstance();
score.render();
}
}
class Foods implements Foodsable {
private static instance: Foods;
elements = document.querySelectorAll<HTMLDivElement>('.food');
private _activeElements: HTMLDivElement[] = [];
private _acriveElementsScore: number[] = [];
get activeElements() {
this._activeElements = [];
this.elements.forEach((element) => {
if (element.classList.contains('food--active')) {
this._activeElements.push(element);
}
});
return this._activeElements;
}
get activeElementsScore() {
this._acriveElementsScore = [];
this.activeElements.forEach((element) => {
const foodScore = element.querySelector('.food__score');
if (foodScore) {
this._acriveElementsScore.push(Number(foodScore.textContent));
}
});
return this._acriveElementsScore;
}
private constructor() {
this.elements.forEach((element) => {
new Food(element);
});
}
static getInstance() {
if (!Foods.instance) {
Foods.instance = new Foods();
}
return Foods.instance;
}
}
const foods = Foods.getInstance();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<div>
<h1>Daily Food Score</h1>
<br />
<div>
<h2>Today's Score</h2>
<div class="score">
<div class="score__circle">
<div class="score__number">0</div>
</div>
</div>
</div>
<br />
<div>
<h2>Good</h2>
<div class="foods-container">
<div class="foods">
<div class="food">
<div class="food__name">Green, leafy vegetables</div>
<div class="food__score">+5</div>
</div>
<div class="food">
<div class="food__name">All other vegetables</div>
<div class="food__score">+5</div>
</div>
<div class="food">
<div class="food__name">Nuts</div>
<div class="food__score">+2</div>
</div>
<div class="food">
<div class="food__name">Whole grains</div>
<div class="food__score">+1</div>
</div>
<div class="food">
<div class="food__name">Fish</div>
<div class="food__score">+4</div>
</div>
<div class="food">
<div class="food__name">Beans</div>
<div class="food__score">+3</div>
</div>
<div class="food">
<div class="food__name">Chicken</div>
<div class="food__score">+2</div>
</div>
<div class="food">
<div class="food__name">
Wine (no more than one glass per day)
</div>
<div class="food__score">+1</div>
</div>
</div>
</div>
</div>
<br />
<div>
<h2>Bad</h2>
<div class="foods-container">
<div class="foods">
<div class="food">
<div class="food__name">Butter and margarine</div>
<div class="food__score">-3</div>
</div>
<div class="food">
<div class="food__name">Cheese</div>
<div class="food__score">-1</div>
</div>
<div class="food">
<div class="food__name">Red meat</div>
<div class="food__score">-3</div>
</div>
<div class="food">
<div class="food__name">Fried food</div>
<div class="food__score">-5</div>
</div>
<div class="food">
<div class="food__name">Sweets</div>
<div class="food__score">-5</div>
</div>
<div class="food">
<div class="food__name">Fast food</div>
<div class="food__score">-5</div>
</div>
<div class="food">
<div class="food__name">Eating out</div>
<div class="food__score">-3</div>
</div>
<div class="food">
<div class="food__name">Wine (more than one glass per day)</div>
<div class="food__score">-3</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>