Course: JavaScript

Progress (0%)

  1. Foundation

  2. Numbers

  3. Strings

  4. Conditions

  5. Loops

  6. Arrays

  7. Functions

  8. Objects

  9. Exceptions

  10. HTML DOM

  11. CSSOM

  12. Events

  13. Drag and Drop

  14. opt Touch Events

  15. Misc

  16. Project: Analog Clock

Project: Analog Clock

Project 1

JavaScript programming

We'll begin with defining the Clock class. As instructed in the description of this project, we must go with the old functional way of defining classes and constructors.

Likewise, here's the Clock() constructor:

function Clock() {}

Now we'll fill this constructor with some initialization code.

As we already know, a Clock instance is an object model around the .clock element. Hence, to store a reference to this corresponding element, we can obtain it as an argument of Clock() and then set the Clock instance's element property to that element.

This takes us to the following code:

function Clock(element) {
   // The corresponding .clock element.
   this.element = element;
}

After this, we need to add all the desired elements into the .clock element. Recall that this task is performed by the addElements() method of the Clock class.

Elements can be added in two ways: either by forming an HTML string of all the elements or by forming a fragment of all the HTML nodes. We'll go with the latter here, since it's relatively easier to set up and implement.

But before that, we'll need a helper function to obtain a <div> element with a given class name applied to it. The benefit of this helper function is that we could reuse it throughout our code.

Here's the helper function getElement():

function getElement(className) {
   var divElement = document.createElement('div');
   divElement.className = className;
   return divElement;
}

Whatever argument is provided to this function, it assigns that to the className property of the created element, which in turn adds it to the class HTML attribute of the element.

And here's the definition of addElements():

Clock.prototype.addElements = function() {
   var fragment = document.createDocumentFragment();
   var element;

   // First add all the graduations.
   for (var i = 0; i < 60; i++) {
      element = getElement('clock_graduation');
      if (i % 5 === 0) {
         element.classList.add('clock_graduation--large');
      }
      element.style.transform = 'rotate(' + (i * 6) + 'deg)';
      fragment.appendChild(element);
   }

   // Then, add the hand nut.
   element = getElement('clock_hand-nut');
   fragment.appendChild(element);

   // Dump the fragment into the .clock element.
   this.element.appendChild(fragment);
}

If you notice, we don't add the .clock elements in here. They'll be dealt with by the ClockHand() constructor.

But before moving on to ClockHand(), there is one thing that we could improve in the code snippet above. Let's get that done.

In the code above, see how we've sprinkled out string literals here and there representing class names of given elements. They surely get the job done, but it isn't considered a good programming practice to hardcode literals deep into code.

Rather, it's much better to create them as global constants and then use those constans instead of those hardcoded literals.

This has the benefit of improved readability of code (on most occasions) and improved flexibility of code. It becomes much easier for us to make changes in any of the constants and then get those changes to be observed in every single place where the constants are used. This can't be achieved with hardcoded literals, where there are always chances of clerical errors.

Likewise, let's replace the literals in the code above with constants whose values are just those same literals.

  • CLOCK_GRADUATION_CLASS holds the class name of a graduation element.
  • CLOCK_GRADUATION_LARGE_CLASS holds the class name of a large graduation element.
  • CLOCK_HAND_NUT_CLASS holds the class name of a hand nut element.

Here's the modified code:

var CLOCK_GRADUATION_CLASS = 'clock_graduation';
var CLOCK_GRADUATION_LARGE_CLASS = 'clock_graduation--large';
var CLOCK_HAND_NUT_CLASS = 'clock_hand-nut';

function Clock() { /* ... */ }

Clock.prototype.addElements = function() {
   var fragment = document.createDocumentFragment();
   var element;

   // First add all the graduations.
   for (var i = 0; i < 60; i++) {
      element = getElement(CLOCK_GRADUATION_CLASS);
      if (i % 5 === 0) {
         // Every fifth element is a large graduation.
         element.classList.add(CLOCK_GRADUATION_LARGE_CLASS);
      }
      element.style.transform = 'rotate(' + (i * 6) + 'deg)';
      fragment.appendChild(element);
   }

   // Then, add the hand nut.
   element = getElement(CLOCK_HAND_NUT_CLASS);
   fragment.appendChild(element);

   // Dump the fragment into the .clock element.
   this.element.appendChild(fragment);
}

Amazing.

The next thing is to create the three hands of the clock via ClockHand(). The type of the hand can be specified as an argument to ClockHand.

But what type of argument to use — a string or a number? Well, we could use a string for this purpose just as we'd use a number — there obviously won't be any noticeable difference between the performance of either (at least in this case).

Let's define three constants to represent the three hands of a clock, respectively:

var CLOCK_HOURS_HAND = 0;
var CLOCK_MINUTES_HAND = 1;
var CLOCK_SECONDS_HAND = 2;

Now, let's come back to the Clock() constructor and initialize its three hands, i.e. hoursHand, minutesHand and secondsHand:

function Clock(element) {
   // The corresponding .clock element.
   this.element = element;

   // Add all the elements.
   this.addElements();

   this.hoursHand = new ClockHand(this, CLOCK_HOURS_HAND);
   this.minutesHand = new ClockHand(this, CLOCK_MINUTES_HAND);
   this.secondsHand = new ClockHand(this, CLOCK_SECONDS_HAND);
}

Alright, with ClockHand() invoked in Clock(), our next job is to get done with its definition:

The very first thing is to set up the clock instance property inside the ClockHand() constructor.

Recall that clock is a way of having access to the owner Clock instance. The reason for having this access is so that we can easily add the current clock hand to the .clock element, from within the ClockHand() constructor.

The code below accomplishes this idea:

function ClockHand(clock, handType) {
   // The owner Clock instance.
   this.clock = clock;
}

Our next job is to create a .clock_hand element, in the ClockHand() constructor, for the current hand. But before that, let's define three constants, as detailed below:

  • CLOCK_HAND_CLASS holds the class name of a clock hand (i.e. 'clock_hand').
  • CLOCK_HAND_CLASSES is an array meant to map from a given clock hand type (i.e. CLOCK_HOURS_HAND, CLOCK_MINUTES_HAND or CLOCK_SECONDS_HAND) to a corresponding class name.

Here's the code defining these two constants followed by the code extending the ClockHand() constructor:

var CLOCK_HAND_CLASS = 'clock_hand';
var CLOCK_HAND_CLASSES = [
   'clock_hand--hours',
   'clock_hand--minutes',
   'clock_hand--seconds'
];
function ClockHand(clock, handType) {
   // The owner Clock instance.
   this.clock = clock;

   // The .clock_hand element.
   this.element = getElement(CLOCK_HAND_CLASS);
   this.element.classList.add(CLOCK_HAND_CLASSES[handType]);
   this.clock.element.appendChild(this.element);
}

In the ClockHand() constructor, a .clock_hand element is created, then assigned to the element property for later access, then given another class based on the type of the current hand, and then finally added to the underlying .clock element.

Simple.

After defining clock and element, the last thing left to be done in the ClockHand() constructor is to define its angleIncrement property.

angleIncrement simply holds the angle by which to rotate the respective hand with every passing second.

It's computed as follows:

  • For the hours hand, its angleIncrement is 360 / (60 * 60 * 12) as it takes 12 hours (60 x 60 x 12 seconds) to complete 360 degrees.
  • For the minutes hand, its angleIncrement is 360 / (60 * 60) as it takes 60 minutes (60 x 60 seconds) to complete 360 degrees.
  • For the seconds hand, its angleIncrement is 360 / 60 as it takes 60 seconds to complete 360 degrees.

In the code below, we set up a few conditionals to define angleIncrement based on the type of the hand:

function ClockHand(clock, handType) {
   // The owner Clock instance.
   this.clock = clock;

   // The .clock_hand element.
   this.element = getElement(CLOCK_HAND_CLASS);
   this.element.classList.add(CLOCK_HAND_CLASSES[handType]);
   this.clock.element.appendChild(this.element);

   // Set angleIncrement based on the type of the hand.
   if (handType === CLOCK_HOURS_HAND) {
      this.angleIncrement = 360 / (60 * 60 * 12);
   }
   else if (handType === CLOCK_MINUTES_HAND) {
      this.angleIncrement = 360 / (60 * 60);
   }
   else {
      this.angleIncrement = 360 / 60;
   }
}

So far, we're doing really good.

At this stage, there are only four methods left to be defined — three for the Clock class and one for the ClockHand class.

Let's begin with the former.

In the code below, we define the startTicking() method:

Clock.prototype.startTicking = function() {
   var time = new Date();
   this.timeInSeconds = time.getSeconds() +
                        time.getMinutes() * 60 +
                        time.getHours() * 60 * 60;

   this.tick();
}

What startTicking() does is to simply retrieve the current time (based on the computer's local time) and then convert it into seconds. This conversion is done in order rotate each of the clock's hands to a given position based on its respective angleIncrement.

The converted time is saved in the timeInSeconds property so that it can be accessed later on by the tick() method to be incremented with every call.

Once the time retrieval and conversion is done, the tick() method is called in order to get the hands of the clock to showcase that very time.

To learn more about retrieving the current time and about methods of a Date object, please refer to JavaScript Dates.

Let's now see the tick() method:

Clock.prototype.tick = function() {
   // Update the clock's hands.
   this.secondsHand.update();
   this.minutesHand.update();
   this.hoursHand.update();

   // Increment by one second.
   this.timeInSeconds++;

   setTimeout(this.tick.bind(this), 1000);
}

First, we call the update() method of each of the hands to update its position with respect to the current time. We'll come to update() in a while.

After the hands get updated to the current time, we obviously need to increment timeInSeconds to get the clock to showcase the next second on the next tick. Then finally, setTimeout() is called to invoke tick() again after one complete second.

After one second, tick() executes again and likewise, the clock's hands get updated to the latest time again, all in all giving the feel of a real analog clock, ticking with every passing second.

Let's get to the update() method of the ClockHand class:

ClockHand.prototype.update = function() {
   this.element.style.transform =
   'rotate(' + (this.angleIncrement * this.clock.timeInSeconds)  + 'deg)';
}

The idea of update() is very simple — given the angle to rotate by for every second and given the current time in seconds, compute the current rotation angle by multiplying both these values together.

Wasn't this simple?

An important thing to note regarding the setTimeout() function is that when the browser tab is not in focus, there is an inconsistency in the firing of the timeout's callback. In our case, this means that when the browser tab is not in focus, our clock might not tick regularly. And then whenever we'd resume back to the tab, the clock might be showing the wrong time.

One solution to this is as follows: stop the clock from ticking when the tab loses focus and then start ticking it again when the tab's focus resumes.

However, the problem with this approach is that if the tab has lost focus but is still visible, such as in a multitasking display, then the clock won't look nice halted in its current position.

A better choice can be to just make sure that when the browser tab gets focus again, the time being displayed is the correct one. This can be done by stopping and then starting the clock again.

What we really need right now is the method restartTicking(). It'll stop the ongoing ticking timeout and then call startTicking().

Needless to say, in order to stop a timeout, we ought to have its ID with us. Hence, before defining restartTicking(), we need to modify tick() to store the ID returned by setTimeout() in the timerId property of the current ClockHand instance.

Consider the following definition of tick():

Clock.prototype.tick = function() {
   // Update the clock's hands.
   this.secondsHand.update();
   this.minutesHand.update();
   this.hoursHand.update();

   // Increment by one second.
   this.timeInSeconds++;

   this.timerId = setTimeout(this.tick.bind(this), 1000);
}

Now, let's get to restartTicking():

Clock.prototype.restartTicking = function() {
   clearTimeout(this.timerId);
   this.startTicking();
}

With the clock's start-stop logic in place, the next thing is to write the code that calls restartTicking() when the browser tab gets focus.

This is done below, in the Clock() constructor:

function Clock(element) {
   /* ... */

   window.addEventListener('focus', this.restartTicking.bind(this));

}

One final thing left is to draft a for loop to iterate over all .clock elements in the document and create a Clock for each of them and get it ticking.

This is what we do below:

var clockElements = document.getElementsByClassName('clock');

for (var i = 0; i < clockElements.length; i++) {
   var clock = new Clock(clockElements[i]);
   clock.startTicking();
}

And this completes this project.