Tim Fletcher
Tim Fletcher

Reputation: 7396

How to make a Meteor template helper reactive without changing collection data

I'm writing a small application that shows live hits on a website. I'm displaying the hits as a table and passing each one to a template helper to determine the row's class class. The idea is that over time hits will change colour to indicate their age.

Everything renders correctly but I need to refresh the page in order to see the helper's returned class change over time. How can I make the helper work reactively?

I suspect that because the collection object's data isn't changing that this is why and I think I need to use a Session object.

Router:

Router.route('/tracked-data', {
  name: 'tracked.data'
});

Controller:

TrackedDataController = RouteController.extend({

  data: function () {
    return {
      hits: Hits.find({}, {sort: {createdAt: -1}})
    };
  }
});

Template:

{{#each hits}}
  <tr class="{{ getClass this }}">{{> hit}}</tr>
{{/each}}

Helper:

Template.trackedData.helpers({
  getClass: function(hit) {
    var oneMinuteAgo = Date.now() - 1*60*1000;
    if (hit.createdAt.getTime() > oneMinuteAgo) {
      return 'success';
    } else {
      return 'error';
    }
  }
});

Upvotes: 0

Views: 627

Answers (1)

Tim Fletcher
Tim Fletcher

Reputation: 7396

I've managed to get this working though I'm not sure it's the 'right' way to do it. I created a function that is called every second to update Session key containing the current time. Then, in my helper, I can create a new Session key for each of the objects that I want to add a class to. This session key is based upon the value in Session.get('currentTime') and thus updates every second. Session is reactive and so the template updates once the time comparison condition changes value.

var updateTime = function () {
  var time = Date.now();
  Session.set('currentTime', time);
  setTimeout(updateTime, 1 * 1000); // 1 second
};
updateTime();


Template.trackedData.helpers({

  getClass: function(hit) {
    var tenMinutesAgo = Session.get('currentTime') - 10*1000,
        sessionName = "class_" + hit._id,
        className;
    className = (hit.createdAt.getTime() > tenMinutesAgo) ? 'success' : 'error';
    Session.set(sessionName, className);
    return Session.get(sessionName);
  }
});

Update

Thanks for the comments. The solution I ended up with was this:

client/utils.js

// Note that `Session` is only available on the client so this is a client only utility.

Utils = (function(exports) {

  return {

    updateTime: function () {
      // Date.getTime() returns milliseconds
      Session.set('currentTime', Date.now());
      setTimeout(Utils.updateTime, 1 * 1000); // 1 second
    },

    secondsAgo: function(seconds) {
      var milliseconds = seconds * 1000; // ms => s
      return Session.get('currentTime') - milliseconds;
    },
  };

})(this);

Utils.updateTime();

client/templates/hits/hit_list.js

Template.trackedData.helpers({
  getClass: function() {
    if (this.createdAt.getTime() > Utils.secondsAgo(2)) {
      return 'success';
    } else if (this.createdAt.getTime() > Utils.secondsAgo(4)) {
      return 'warning';
    } else {
      return 'error';
    }
  }
});

Upvotes: 0

Related Questions