[SOLVED] Jquery Countdown no reactive

Hellooo community,

here’s a very compelling problem.
I use the library : https://github.com/hilios/jQuery.countdown and it works fine except when there’s a date change.

So the problem is there, when I change the date from my mongodb database, the date with the jquery countdown library does not change, while on the HTML DOM, the date has been updated.

The library isn’t reactive, maybe that’s the problem? Do you have a similar but reactive library to offer me?

Otherwise, here is my code to display my countdown:

onRendered :

Template.departs.onRendered(function(){
    this.autorun(function() {

        window.numberOfChild.get();

        $('[data-countdown]').each(function () {
            let $this = $(this), finalDate = moment($(this).data('countdown')).utc().format("Y-M-D H:m:s");
            $this.countdown(finalDate, function (event) {
                if (event.strftime("%D") <= "00") {
                    if(event.strftime("%H") > "00")
                        $this.html(event.strftime("%Hh%M"));
                    else if(event.strftime("%M.%S") <= "01.00") {
                        $this.html("Proche");
                        if (event.strftime("%M.%S") <= "00.00")
                            $(this).closest('tr').remove();
                    }
                    else
                        $this.html(event.strftime("%M min"));
                }
                else
                    $this.html(event.strftime("%Dj %Hh%M"))
            });
        });
    });
});

depart.html :

<template name="departs">
    <table class="horaire-arret">
        <thead>
            <tr>
                <th>Ligne</th>
                <th>Destination</th>
                <th>Départ</th>
            </tr>
        </thead>
        <tbody>
            {{#each depart}}
            <tr class="{{@index}}">
                <td><img src="/images/picto/{{numeroVehicule}}.{{jpgOrGif numeroVehicule}}" alt="ligne"></td>
                <td>{{nomDestination}}</td>
                <td data-countdown="{{horaireRealiseArrivee}}" data-id="{{_id}}">{{#if perturbation}}<img src="/images/picto/picto-trafic.png" alt="info-trafic">{{/if}}</td>
            </tr>
            {{/each}}
        </tbody>
    </table>
</template>

Thanks for your help!

PS : I’m using Meteor V 1.6.1.2

So, you have a reactive context (what’s inside the autorun), but there are no reactive data sources in there that I can see.

Which date is being changed in that code? Basically, you want to have some way of subscribing to that date, and getting it with a Collection.find inside the autorun. The autorun will only re-run if the date changes.

I’m not sure if that helps - ask again if not :slight_smile:

Hello robfallows,
thank you for ur answer.

I will try to explain my problem better.

I want to be able to view the data on this page even when we are offline (without ethernet or wii-fi connection) for a while. This system is already in place and works very well (I send 2 days of data directly to the browser with my Departs.helpers).

Now, my concern is with updating the time displayed through the “the final countdown” library.

Here are two screenshots to illustrate what I say.

This first image at the date at : “wed jul 04 2018 21:38:30 GMT+0200 (CEST)”

The second screenshot shows the same line with the modified date at : “wed jul 04 2018 21:40:30 GMT+0200 (CEST)”

but, as you can see, both screenshots show 18 min, the second screenshot should have shown 20 min.
I tried to use the autorun to restart the system that displays the time but nothing changes :frowning:

That’s what I tried to do:
Depart.html (or in onCreated) :

     setTimeout(function() {
                $("[data-countdown]").each(function (index) {
                    let $this = $(this), finalDate = moment($(this).data('countdown')).utc().format("Y-M-D H:m:s"), _id = $(this).data('id');
                    if(Object.keys(window.dataHoraire.get()).length === 0 || (window.dataHoraire.get().hasOwnProperty(_id) && window.dataHoraire.get()[_id] !== String(finalDate))) {
                        if (index <= window.numberOfLineHoraire) {
                            finalDate = moment($(this).data('countdown')).subtract(2, 'hours').format("Y-M-D H:m:s");
                            _id = $(this).data('id');
                            window.dataArray[_id] = finalDate;
                            console.log("ok");
                        }
                        window.dataHoraire.set(window.dataArray);
                        // there, the console log return the old date, not the new date... why ? 
                        console.log(window.dataHoraire.get()[_id]);
                    }
                });
        }, 5000);

horaire.js :

Template.departs.onRendered(function(){
    this.autorun(function() {

        window.numberOfChild.get();
        window.dataHoraire.get();
        $('[data-countdown]').each(function () {
            let $this = $(this), finalDate = moment($(this).data('countdown')).utc().format("Y-M-D H:m:s");
            $this.countdown(finalDate, function (event) {
                if (event.strftime("%D") <= "00") {
                    if(event.strftime("%H") > "00")
                        $this.html(event.strftime("%Hh%M"));
                    else if(event.strftime("%M.%S") <= "01.00") {
                        $this.html("Proche");
                        if (event.strftime("%M.%S") <= "00.00")
                            $(this).closest('tr').remove();
                    }
                    else
                        $this.html(event.strftime("%M min"));
                }
                else
                    $this.html(event.strftime("%Dj %Hh%M"))
            });
        });
    });
});

EDITE :
I noticed that when I change the date from mongodb, meteor changes the HTML DOM well, but, when I get the change from the DOM with jquery, it always shows me the old date, I don’t understand why.
Maybe that’s the problem.

I’ve another question, when a data is updated from mongodb and meteor automatically sends the change to the client, the DOM is either modified and taken into account or the find() must be restarted for it to be taken into account in the DOM?

For example, when I delete data, it is automatically deleted from the browser without me restarting a find(), it’s the same for adding data no?

I’ll answer these questions first.

If you are subscribed to a collection, whenever there are changes to that collection (add, modify, delete), and those changes qualify to be sent to the client (the find used in the publication would “see” those changes), then the client gets them.

Just because the client gets the changes doesn’t mean you will see those changes: the find on the client is a reactive data source, however to see the changes you must put the find (or another reactive data source, like ReactiveVar) inside an autorun.

Sometimes the autorun is provided for you. For example, Blaze helpers, which is why these ensure the DOM is updated as reactive data changes.

An autorun has the following behaviour:

  • It always runs once.
  • If it finds any reactive data sources it sets up dependencies on these.
  • If any dependencies change it re-runs from the top.

Note the second point. If there are no reactive data sources it will never run again.

I cannot see any reactive variables in your autorun, unless window.numberOfChild and / or window.dataHoraire are ReactiveVars or similar?

1 Like

Ok thank you, I see and understand better.

window.numberOfChild and window.dataHoraire are ReactiveVars.

Template.horaire.onCreated(function() {
    /**
     * onCreated
     * A la création du template horaire, s'abonner à la collection xxxx
     * Les ReactiveVar sevent à recharger les subscribes lorsqu'un changement est effectué
     * cela permet de pouvoir afficher ce changement (ex : Supprimer une ligne lorsque le véhicule est théoriquement passé)
     */
    window.countdownAtZero = new ReactiveVar(false);
    window.timerExpired    = new ReactiveVar(false);
    window.numberOfChild   = new ReactiveVar($("[data-countdown]").length);
    window.dataArray           = {};
    window.numberOfLineHoraire = 4;
    window.dataHoraire     = new ReactiveVar([]);

// This method allows to refresh the display time when a new data is recorded and appears.
    Meteor.setInterval(function(){
        if(parseInt($("[data-countdown]").length) !== parseInt(window.numberOfChild.get())){
            window.numberOfChild.set($("[data-countdown]").length);
        }
    }, 500);

    /**
     * @collections subscribe
     */
    this.subscribe('arret',       Router.current().params.arret);
    this.subscribe('depart', Router.current().params.arret);
    this.subscribe('infostrafic', window.timerExpired.get());
    this.subscribe('infopubs',    Router.current().params.arret, window.countdownAtZero.get());
});

but when I update window.dataHoraire.set(), my function in the autorun of Template.departs.onRendered it does not update my time despite the fact that the function restarts correctly.

Hmm. Using window.numberOfChild.get() or window.dataHoraire.get() will cause the autorun to rerun if you set one of those variables. However, you are not doing anything with the returned data (you would normally use something like const abc = window.dataHoraire.get(); and then use the new value of abc to change what the rest of the code does.

I notice that in Template.horaire.onCreated you have these subscriptions:

  this.subscribe('arret',       Router.current().params.arret);
  this.subscribe('depart', Router.current().params.arret);
  this.subscribe('infostrafic', window.timerExpired.get());
  this.subscribe('infopubs',    Router.current().params.arret, window.countdownAtZero.get());

which all have reactive variables as parameters. Are you wanting to resubscribe when they change? If so, you will need to put them inside an autorun:

this.autorun(() => {
  this.subscribe('arret',       Router.current().params.arret);
  this.subscribe('depart', Router.current().params.arret);
  this.subscribe('infostrafic', window.timerExpired.get());
  this.subscribe('infopubs',    Router.current().params.arret, window.countdownAtZero.get());
});

Maybe that’s where this is not working for you?

it’s not that :frowning:
could we make an appointment to show you my problem with Teamviewer software ?
If yes, I’ll let you contact me by private message!

So, I’ve looked a little closer at what you’ve provided. I think I understand what you’ve tried to do:

  • You change the date in the database.
  • Blaze renders the new date in the DOM.
  • You read the new value from the DOM and adjust the countdown.

Is that right?

1 Like

That’s exactly it!!
Except in step three (“You read the new value from the DOM and adjust the countdown.”), nothing changes.

When I get the new value from the DOM with JQUERY, I have the old value and not the new one. While on the DOM, I see that blaze has returned the new value.

OK. This is an unfortunate side-effect of the way you’re mixing imperative code (jQuery) with declarative code (Blaze) and is made worse by trying to cope with aligning the state of the DOM between the two.

You can see the problem clearly where you have used a setTimeout in your Template.departs.onCreated. That is a clear indicator that you have found that the DOM was not ready when you expected it to be. It is possible to work with jQuery and Blaze so that you don’t have to do this, but you are where you are right now, and probably don’t feel too much like refactoring :wink:.

My suspicion is you have a related issue with your countdown timer. By the time Blaze has seen the reactive change and updated the DOM, the countdown autorun has already run (and picked up the old data).

Can you resolve by using the new time (is that from window.dataHoraire.get()?) to set the countdown timer directly in your Template.departs.onRendered autorun (don’t read from the DOM).

2 Likes

I’m sorry, I was in a meeting.

I’ll test this and keep you posted!
Thank you again.

1 Like

Done, the solution lies in the use of observeChanges (I didn’t know it existed at all.).
Thanks to you Robfallows, without you I may never have tried to rethink my code.
ended setTimeout or setInterval !

2 Likes