11

I am building a chat application and on my "new chats" page I have a list of contacts, which you can select one by one by tapping them (upon which I apply a CSS selected class and push the user id into an array called 'newChatters'.

I want to make this array available to a helper method so I can display a reactive list of names, with all users who have been added to the chat.

The template that I want to display the reactive list in:

<template name="newChatDetails">
  <div class="contactHeader">
    <h2 class="newChatHeader">{{newChatters}}</h2>
  </div>
</template>

The click contactItem event triggered whenever a contact is selected:

Template.contactsLayout.events({
 'click #contactItem': function (e) {
   e.preventDefault();
   $(e.target).toggleClass('selected');
   newChatters.push(this.username);
...

The newChatters array is getting updated correctly so up to this point all is working fine. Now I need to make {{newChatters}} update reactively. Here's what I've tried but it's not right and isn't working:

Template.newChatDetails.helpers({
  newChatters: function() {
    return newChatters;
  }
});

How and where do I use Deps.autorun() to make this work? Do I even need it, as I thought that helper methods auto update on invalidation anyway?

Giacomo1968
  • 25,759
  • 11
  • 71
  • 103
jawad-uk
  • 223
  • 3
  • 9

5 Answers5

14

1) Define Tracker.Dependency in the same place where you define your object:

var newChatters = [];
var newChattersDep = new Tracker.Dependency();

2) Use depend() before you read from the object:

Template.newChatDetails.newChatters = function() {
  newChattersDep.depend();
  return newChatters;
};

3) Use changed() after you write:

Template.contactsLayout.events({
  'click #contactItem': function(e, t) {
    ...
    newChatters.push(...);
    newChattersDep.changed();
  },
});
lakenen
  • 3,436
  • 5
  • 27
  • 39
Hubert OG
  • 19,314
  • 7
  • 45
  • 73
6

You should use the Session object for this.

Template.contactsLayout.events({
 'click #contactItem': function (e) {
   //...
   newChatters.push(this.username);
   Session.set('newChatters', newChatters);
 }
});

and then

Template.newChatDetails.helpers({
  newChatters: function() {
    return Session.get('newChatters');
  }
});
jacksondc
  • 600
  • 1
  • 6
  • 19
3

You could use a local Meteor.Collection cursor as a reactive data source:

var NewChatters = new Meteor.Collection("null");

Template:

<template name="newChatDetails">
  <ul>
    {{#each newChatters}}
      <li>{{username}}</li>
    {{/each}}
  </ul>
</template>

Event:

Template.contactsLayout.events({
  'click #contactItem': function (e) {
    NewChatters.insert({username: this.username});
  }
});

Helper:

Template.newChatDetails.helpers({
  newChatters: function() { return NewChatters.find(); }
});
Donny Winston
  • 2,324
  • 1
  • 15
  • 13
  • didn't think of this. I'll prob just use the session object here per another answer below but this is another helpful approach, thanks – jawad-uk May 04 '14 at 08:35
  • 1
    var NewChatters = new Meteor.Collection(null); If you put null in quotes it just names your collection "null" and still syncs it with server. – sday Jun 21 '15 at 12:51
3

To mimick the behaviour of Session without polluting the Session, use a ReactiveVar:

 Template.contactsLayout.created = function() {
      this.data.newChatters = new ReactiveVar([]);
 }

 Template.contactsLayout.events({
      'click #contactItem': function (event, template) {
            ...
            template.data.newChatters.set(
                template.data.newChatters.get().push(this.username)
            );
           ...

Then, in the inner template, use the parent reactive data source:

 Template.newChatDetails.helpers({
      newChatters: function() {
           return Template.parentData(1).newChatters.get();
      }
 });
MattiSG
  • 3,796
  • 1
  • 21
  • 32
  • I think this is the best answer. One suggestion, however, might be to move the reactive var outside of the template data object and simply into the template instance itself. This keeps the data object uncluttered. – Adam May 17 '15 at 19:57
  • @Adam No, you can't take this out from the `data` object, because that's exactly the trick that allows you to access parent data from a template: with the `parentData` method. – MattiSG May 18 '15 at 21:20
  • not natively, anyway... http://stackoverflow.com/questions/27949407/how-to-get-the-parent-template-instance-of-the-current-template – Adam May 18 '15 at 21:35
1

for people who is looking for a workaround for this in the year 2015+ (since the post is of 2014).

I'm implementing a posts wizard pw_module where I need to update data reactively depending on the route parameters:

Router.route('/new-post/:pw_module', function(){
    var pwModule = this.params.pw_module;
    this.render('post_new', {
        data: function(){
            switch (true) {
                case (pwModule == 'basic-info'):
                    return {
                        title: 'Basic info'
                    };
                    break;
                case (pwModule == 'itinerary'):
                    return {
                        title: 'Itinerary'
                    };
                    break;
                default:
            }
        }
    });
}, {
    name: 'post.new'
});

Later in the template just do a:

<h1>{{title}}</h1>

Changing routes

The navigation that updates the URL looks like this:

<nav>
    <a href="{{pathFor route='post.new' pw_module='basic-info'}}">Basic info</a>
    <a href="{{pathFor route='post.new' pw_module='itinerary'}}">Itinerary</a>
</nav>

Hope it still helps someone.

Gus
  • 6,545
  • 4
  • 36
  • 39