Many instances of the same LiveView hook?

Is it valid to use the same hook on several elements?
for example:

<div id="item-1" phx-hook="Countdown"> .... </div>
 <div id="item-2" phx-hook="Countdown"> .... </div>
1 Like

Yes it is :blush:

2 Likes

Thanks.
I’m not a fan of Javascript and really I can’t understand how to set the hook up.

The goal is to have multiple countdowns in a liveview, every countdown element would have a unique ID and some data set, and they will be updated by timers set on the javascript code.

It works for one timer, but when I add a second countdown element, it disregard the first one and start counting the new one.

My code is the following :

import setCountdown from './setCountdown';

const Countdown = {
  mounted() {
    timer = [];
    auction_id = this.el.id.split('-').slice(-1)[0];
    console.log(auction_id);
    timer[auction_id] = setCountdown(this);
     console.log(timer, " in mounted");
  },
  beforeUpdate() {
    clearInterval(timer[auction_id]);
  },
  updated() {
    timer[auction_id] = setCountdown(this);
  },
  destroyed() {
    clearInterval(timer[auction_id]);
  },
};

export default Countdown;

and the setCountdown function :

function setCountdown(hook) {
  elementId = hook.el.id;
  var timerEnd = hook.el.dataset.end_date;
  console.log(timerEnd);

  var countDownDate = new Date(timerEnd);

  interval = setInterval(function () {
    var now = new Date().getTime();

    var distance = countDownDate.getTime() - now;
    document.getElementById(elementId).innerHTML = msToTime(distance);

    if (distance < 0) {
      clearInterval(interval);
      document.getElementById(elementId).innerHTML = 'EXPIRED';
    }
  }, 1000);

  return interval;
}

export default setCountdown;

the liveview template :

 <p >Time for initial bids : <span id={"countdown-for-phase2-#{@auction.auction_id}"} phx-hook="Countdown" data-end_date={@auction.phase1_ends} ></span></p>

What do you think?

1 Like

Nobody likes JavaScript :wink:. But it’s a necessary evil.

Why are you initializing the timer array on the mount of each hook if it’s supposed to hold all of them? But more importantly, why is it an array? Try this.timer = ... so each hook has it’s own timer.

1 Like

Thank you, for your suggestion.
The js code had also other bugs, but I managed to get it to work.
If anyone cares here is the altered code that works with multiple countdown DOM elements with the same hook :

countdown.js

import setCountdown from './setCountdown';

const Countdown = {
  mounted() {
    this.timer = setCountdown(this);
  },
  beforeUpdate() {
    clearInterval(this.timer);
  },
  updated() {
    this.timer = setCountdown(this);
  },
  destroyed() {
    clearInterval(this.timer);
  },
};

export default Countdown;

setCountdown.js

function msToTime(duration) {
  var seconds = Math.floor((duration / 1000) % 60),
    minutes = Math.floor((duration / (1000 * 60)) % 60),
    hours = Math.floor((duration / (1000 * 60 * 60)) % 24);

  hours = hours < 10 ? '0' + hours : hours;
  minutes = minutes < 10 ? '0' + minutes : minutes;
  seconds = seconds < 10 ? '0' + seconds : seconds;

  return hours > 0 ? hours + ':' : '' + minutes + ':' + seconds;
}

function setCountdown(hook) {
  var timerEnd = hook.el.dataset.end_date;

  var countdownDate = new Date(timerEnd);

  var interval = setInterval(function () {
    var now = new Date().getTime();

    var distance = countdownDate.getTime() - now;

    document.getElementById(hook.el.id).innerHTML = msToTime(distance);

    if (distance < 0) {
      clearInterval(interval);
      document.getElementById(hook.el.id).innerHTML = 'EXPIRED';
    }
  }, 1000);

  return interval;
}

export default setCountdown;

part of the heex template

 <%= for auction <-  @auctions do %>
        <div>
          <h3>Auction <%= auction.auction_id %></h3>
          <p>Time for initial bids : <span id={"countdown-for-phase2-#{auction.auction_id}"} phx-update="ignore" phx-hook="Countdown" data-end_date={auction.phase1_ends} ></span></p>
          <p> Time for auction to end : <span id={"countdown-for-end-#{auction.auction_id}"}  phx-update="ignore" phx-hook="Countdown" data-end_date={auction.phase2_ends} ></span></p>
          <p>Decision Period :  <span id={"countdown-for-decision_period-#{auction.auction_id}"} phx-update="ignore" phx-hook="Countdown" data-end_date={auction.decision_period_ends} ></span> </p>
        </div>
        
<% end %>
1 Like