Meteor Dynamically Create Objects and refer to them? // Is there a simple way to do this

1.1k views Asked by At

So, im really new to both JavaScript and Meteor, so I only know a handful of things.

Learning both, JavaScript and Meteor I set myself the challenge to make a 3 by 3 field of with individual counters in them, that count, if you click the cell.

I reached my first goal: http://9fields.meteor.com/

The second goal I set for myself was to try to make it easily expandable. Say, i want to make the same field but 7x7.

Every cell has its own template.

<template name="zelle1">                      
  <td class="box">                          
    <p class="textinbox">{{counter}}</p>  
  </td>                                     
</template>                                   

Every cell has its own counter and every cell has one event handler and one helper to register a click

Template.zelle1.helpers({
counter: function () {
  return Session.get("counter1");
}

});

Template.zelle1.events({
"click td": function() {
  return Session.set("counter1", Session.get("counter1")+1);
}
});

I then pasted the code 8 more times and replaced the numbers.

The only thing that i feel reaches my criteria for the second goal is the way i create the table.

<table>                                         
    {{#each zellen}}                            
        {{#if this.newrow}}                     
            <tr></tr>                           
            {{> UI.dynamic template=this.name}} 
        {{else}}                                
            {{> UI.dynamic template=this.name}} 
        {{/if}}                                 
    {{/each}}                                   
</table>                                        

in combination with this Array (array is the right word?)

Template.body.zellen = [
{name: "zelle1", newrow: false},
{name: "zelle2", newrow: false},
{name: "zelle3", newrow: false},
{name: "zelle4", newrow: true},
{name: "zelle5", newrow: false},
{name: "zelle6", newrow: false},
{name: "zelle7", newrow: true},
{name: "zelle8", newrow: false},
{name: "zelle9", newrow: false},
];

My question: Do i have to make 9 Templates and 9 helpers and 9 events for those 9 fields? Meaning that if i want to make a 7x7 field i need to paste the code 49 times? Or is there a more efficient way to do this?

Already Thank you for reading through this!

3

There are 3 answers

0
Stephan On BEST ANSWER

Let's propose a third way to approach the problem :) You can always use something like the following to get multiple Session variables filled:

var fieldSize = 9;
for (i = 1; i <= fieldSize; i++) { 
    Session.set("counter" + i, i); // sets 9 Session variables
}

Or perhaps use an array inside the Session. It can even be an array of objects:

var fieldSize = 9;
var fields = [];
for (i = 1; i <= fieldSize; i++) {
    fields.push({name: i, count: i});
}
Session.set("fields", fields); 

This is basically the same approach as using ReactiveVar, only that you are using a global variable (Session) instead of a scoped one. You entire application will be able to access the things you store within Session.

Also, you should perhaps not use a template bound to each field, but iterate using {{#each}}

<template name="fields">                      
  {{#each withIndex fields}}
     {{> zelle}}
  {{/each}}                                    
</template>  

The trouble here is that you have no control over which cell you are rendering currently, so you need to add an index to each of the {{#each}} runs. This is why I suggest a new global helper withIndex:

Template.registerHelper('withIndex', function (list) {
var withIndex = _.map(list, function (v, i) {
    v.index = i;
    return v;
});
return withIndex;
});

You can use the new index just like any other field from the array. You will need it to determine which number was clicked and where to increase the counter. I've used the td's id value to store the index of which cell was clicked.

<template name="zelle">                      
  <td class="box" id="zelle-{{index}}">                          
    <p class="textinbox">{{name}} with a count of {{count}}</p>  
  </td>                                     
</template>  

Template.fields.helpers({
    fields: function () {
        return Session.get("fields");
    }
});
0
sdooo On

Before you start reading you should get familiar with Meteor collections because this is the best way to do your example, and how "this" works inside of #each loop

You can create one template and pass data to this

<template name="zelle">                      
  <td class="box">                          
    <p class="textinbox">{{name}}</p>  
  </td>                                     
</template> 

And main template can look something like this

<table>
  {{#each zellen}}
   <tr>
   {{#each zellen}}
     {{>zelle}}
   {{/each}}
   </tr>
   {{/each}}
</table>

This first #each can have any other helper, but if you want to have square you need same count of loops through <td> and <tr>

I would rather make a collection to hold your data

On client side you can make something like this:

yourCollection= new Meteor.Collection('yourCollection');

Meteor.startup(function(){
   If(yourCollection.find().count()==0){
   yourCollection.insert({name:1});
   yourCollection.insert({name:2});
   etc...
}
});

And event can look like this:

"click td": function() {
  yourCollection.update({_id:this._id},{$inc:{name:1}});
}

Since you now have a collection zellen helper should look like

zellen: function(){
  return yourCollection.find();
}
0
David Weldon On

The answer Sindis gave looks correct, assuming you want to use a collection to manage the reactivity. In most cases you do, but not always. I'll propose an alternative solution which uses scoped reactivity where each template keeps track of it's own state without the need for global session variables. Warning - this is a somewhat advanced topic. Here's a working solution:

app.html

<body>
  <table>
    {{#each zellen}}
      {{#if newrow}}
        <tr></tr>
      {{/if}}
      {{> zelle}}
    {{/each}}
  </table>
</body>

<template name="zelle">
  <td class="box">
    <p class="textinbox">{{counter}}</p>
  </td>
</template>

app.js

if (Meteor.isClient) {
  Template.body.helpers({
    zellen: function() {
      var rows = 3;
      var columns = 4;

      // dynamically build the zellen
      var zellen = _.times(rows * columns, function(i) {
        var newrow = false;
        if ((i % columns) === 0)
          newrow = true;

        return {count: i + 1, newrow: newrow}
      });

      return zellen;
    }
  });

  Template.zelle.created = function() {
    this.counter = new ReactiveVar;
    this.counter.set(this.data.count);
  };

  Template.zelle.helpers({
    counter: function () {
      return Template.instance().counter.get();
    }
  });

  Template.zelle.events({
    click: function(e, template) {
      var count = template.counter.get();
      template.counter.set(count + 1);
    }
  });
}

Note you will need to run meteor add reactive-var for this to work.