Implementing a timer page using node/express

Issue

I am trying to build a Pomodoro timer, using expressjs. I’ve made the pages with EJS, and gave name attributes to each to tap into them in the server. However I am quite confused if I should wrap each button in a form, and make separate post routes for each of their functionalities in my app.js file to "capture" the click?

I think this would be much easier to make just using plain JavaScript, but I’m trying to make this a full stack kind of application to show that I am familiar with node and express.

I will share the page rendered and the EJS code for it. App.js is pretty much empty right now except some boilerplate/regular routings to other pages. I think I have a good idea on how to implement the timer in javascript, I’m just not sure how to implement that in node/express. I hope my question is clear, I’ve just started using Stackoverflow so bear with me please 🙂
Pomodoro timer page

<!-- Timers -->
<div class="row p-1">
  <p class="col-lg-4 pomodoro-timer" name="w_minutes">25<span class="colon">:</span><span name="w_seconds"></span>00</p>
  <p class="col-lg-4 pomodoro-timer pomodoro-cycles" name="cycles">0</p>
  <p class="col-lg-4 pomodoro-timer" name="b_minutes">5<span class="colon">:</span><span name="b_seconds"></span>00</p>
</div>

<!-- Timer buttons -->
<div class="row mt-2">
  <div class="col-lg-4 pomodoro-button">
    <button class="btn btn-lg btn-light" type="button" name="startButton">Start</button>
  </div>
  <div class="col-lg-4 pomodoro-button">
    <button class="btn btn-lg btn-danger" type="button" name="resetButton">Reset</button>
  </div>
  <div class="col-lg-4 pomodoro-button">
    <button class="btn btn-lg btn-warning" type="button" name="pauseButton">Pause</button>
  </div>
</div>

Solution

Calling the backend API back and forth will introduce time delay, which might not be an ideal solution for high accuracy timer. My suggestion is to create a public JS file which contains the timer logic, put it public folder along with css and other public assets.

However, the idea of polling backend API every second may work if you are creating a monitoring dashboard. The data point is usually persisted in a database with timestamp, let say every second. Although, the roundtrip operation may cause slight time delay, we are still under the illusion of accuracy because we are referring to the timestamp of data point visually, not the actual roundtrip time.

Still, I will provide the example of implementation based on the suggestion above, to give you a basic idea how to implement this solution using Node-Express-EJS. Feel free to modify as per your requirements.

app.js

const path = require("path");
const express = require("express");

const app = express();

app.set("view engine", "ejs");
app.set("views", path.join(__dirname, "views"));

app.use(express.static(path.join(__dirname, "public")));

app.get("/", (req, res, next) => {
  res.render("template", {
    pageTitle: "pomodoro",
  });
});

app.listen(3000);

/views/template.ejs

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title><%= pageTitle %></title>
    <link rel="stylesheet" href="/css/styles.css" />
    <script src="/js/main.js"></script>
  </head>
  <body>
    <main>
      <div>
        <p id="hour">00</p>
        <p class="sep">:</p>
        <p id="minute">00</p>
        <p class="sep">:</p>
        <p id="second">00</p>
      </div>
    </main>
    <button onclick="startCounter()">Start</button>
    <button onclick="pauseCounter()">Pause</button>
    <button onclick="stopCounter()">Stop</button>
  </body>
</html>

/public/js/main.js

let interval;
let isPaused = false;

function startCounter() {
  const hourTag = document.getElementById("hour");
  const minuteTag = document.getElementById("minute");
  const secondTag = document.getElementById("second");

  if (!isPaused) {
    hourTag.innerText = "00";
    minuteTag.innerText = "25";
    secondTag.innerText = "00";
  }

  isPaused = false;

  interval = setInterval(() => {
    let hour = parseInt(hourTag.innerText);
    let minute = parseInt(minuteTag.innerText);
    let second = parseInt(secondTag.innerText);

    if (hour === 0 && minute === 0 && second === 0) {
      clearInterval(interval);
      return;
    }

    second -= 1;

    if (second < 0) {
      second = 59;
      minute -= 1;
    }

    if (minute < 0) {
      minute = 59;
      hour -= 1;
    }

    hourTag.innerText = hour.toString().padStart(2, "0");
    minuteTag.innerText = minute.toString().padStart(2, "0");
    secondTag.innerText = second.toString().padStart(2, "0");
  }, 1000);
}

function pauseCounter() {
  isPaused = true;
  clearInterval(interval);
}

function stopCounter() {
  clearInterval(interval);
  document.getElementById("hour").innerText = "00";
  document.getElementById("minute").innerText = "00";
  document.getElementById("second").innerText = "00";
}

/public/css/styles.css

* {
  margin: 0;
  padding: 0;
}

html {
  box-sizing: border-box;
}

main {
  display: flex;
  flex-direction: row;
}

p {
  display: inline-block;
}

Answered By – dave.tdv

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published