DEV Community

Raymond Camden
Raymond Camden

Posted on • Originally published at raymondcamden.com on

Creating a Live Time Duration Component in Vue.js

Pardon the somewhat awkward title of the post. Today’s Vue.js demo is based on something I saw recently on the cnn.com site - a live update of how long the government shutdown has been going on. They don’t have it online now, but it basically showed this…

Partial government shutdown has gone on for 11d 15h 49m 7s

The values were updated in real time. You could argue (and I’d agree) that it was a bit overly dramatic for a news site (and maybe that’s why it isn’t there now), but when I saw it, I thought it would be something fun to build in Vue. I ended up with not one, but two iterations of the idea and I’d like to share them below. As always, I welcome your comments about what could be improved. Let’s get started.

Version One

The initial version began with a simple set of features. The component should accept a date value (either in the past or future) and then simply display the duration while updating it automatically. Here’s an example of how it could be used:

<div id="app" v-cloak>

  My birthday was <time-since :date="birthday"></time-since> ago. 

</div>
Enter fullscreen mode Exit fullscreen mode

And here’s the JavaScript code behind it. First, just the Vue app itself:

const app = new Vue({
  el:'#app', 
  data:{
    birthday:new Date(2018, 3, 8)
  }
})
Enter fullscreen mode Exit fullscreen mode

As you can see, all I bothered to add was a value for the date. The real meat is in the component:

Vue.component('time-since',{
  template:`
<span> days hours minutes seconds</span>
`,
  data() {
    return {
      interval:null,
      days:0,
      hours:0,
      minutes:0,
      seconds:0,
      intervals:{
        second: 1000,
        minute: 1000 * 60,
        hour: 1000 * 60 * 60,
        day: 1000 * 60 * 60 * 24
      }
    }
  },
  props:{
    date:{
      required:true
    }
  },
  mounted() {
    this.interval = setInterval(() => {
      this.updateDiffs();
    },1000);

    this.updateDiffs();
  },
  destroyed() {
    clearInterval(this.interval);    
  },
  methods:{
    updateDiffs() {
      //lets figure out our diffs
      let diff = Math.abs(Date.now() - this.date.getTime());
      this.days = Math.floor(diff / this.intervals.day);
      diff -= this.days * this.intervals.day;
      this.hours = Math.floor(diff / this.intervals.hour);
      diff -= this.hours * this.intervals.hour;
      this.minutes = Math.floor(diff / this.intervals.minute);
      diff -= this.minutes * this.intervals.minute;
      this.seconds = Math.floor(diff / this.intervals.second);
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

Alright, so let’s tackle it from the top to the bottom. The template is rather simple, and hard coded, to display the duration as:

<span>X days Y hours Z minutes A seconds</span>
Enter fullscreen mode Exit fullscreen mode

There’s no options here to change that. The next block handles the data for the component with the only interesting part (in my opinion) being the math set up to remember various millisecond based intervals.

Next look at mounted and destroyed. mounted is responsible for setting up a second based interval to update the display (and running it right away). destroyed handles removing the interval if the component is removed from the DOM completely.

Finally, updateDiffs just handles doing the math. Something tells me this part could probably be written in less lines of code by people smarter than me, but it worked so I left it alone.

You can view the complete code (and play with it) in this CodePen:

Pretty cool, right? But let’s look at how we can kick it up a notch.

Version the Second

So one of the issues with the first version is that it forces a particular kind of output. What if you wanted to customize the display a bit? That’s where slots come in. Check out this version:

<div id="app" v-cloak>

  My birthday was <time-since :date="birthday">
  <template slot-scope="int">
  D H M S
  </template>
  </time-since> ago. 

</div>
Enter fullscreen mode Exit fullscreen mode

In this version, I’m using a slot and customizing the labels used for the intervals to make it a bit closer to the CNN version. If I wanted to, I could even get rid of the seconds value to make it a bit less distracting. Let’s look at the updated component.

Vue.component('time-since',{
  template:`
<span>
<slot :days="days" :hours="hours" :minutes="minutes" :seconds="seconds"> days hours minutes seconds</slot>
</span>
`,
 //stuff deleted...
});
Enter fullscreen mode Exit fullscreen mode

The change was rather minor. Now the template supports default output (the same as the previous version) but also binds values for all four intervals that can be used in the markup. The text inside that slot will only be used if you don’t pass a slot in. Now the component supports the same output as before but also complete customization.

You can see the output here:

Ok, so what do you think?

Top comments (0)