How to create a dynamic table with checkboxes

I have this table below which populates its headings and data programmatically and provides checkboxes to assign data to the headings (synth parameters). In the app the number of headings will be changing. So my question is how to make the number of the checkboxes match the headings on change.

Here is my HTML right now.

<table class="responsive-table-input-matrix">
	<thead>
        <tr>
    {{#each listParams}}
           <th>{{this}}</th>
            {{/each}}
        </tr>
      </thead>
    
<tbody>
{{#each dataItems}}
<tr>
 <td>{{this}}</td>
	<td><input type="checkbox" value={{this}}></td>
</tr>
   {{/each}}
    </tbody>

I would appreciate this very much as this is my first Meteor app and I am kind of stuck right now.

What you need is the each … in helper.

Here’s a quick example, it should mostly do what you want I think :slight_smile:

<table class="responsive-table-input-matrix">
  <thead>
    <tr>
      {{#each field in listParams}}
        <th>{{field}}</th>
      {{/each}}
    </tr>
  </thead>
    
  <tbody>
    {{#each item in dataItems}}
      <tr>
        {{#each field in listParams}}
          <td>{{item}} <input type="checkbox" value={{item}}></td>
        {{/each}}
      </tr>
   {{/each}}
  </tbody>
</table>

Edit:
Instead of {{item}} it should be something like {{item[field]}}, though I don’t remember if you can do that in Blaze. Maybe you need to create a helper for that?

2 Likes

You’ll need a:

<th>Item name</th>

after the <tr> and before the first {{#each ...

And you’ll need:

<td>{{name}}</td>
{{#each checkboxes}}<td><input type="checkbox" checked={{this}}></td>{{/each}}

instead of:

<td>{{this}}</td>
<td><input type="checkbox" value={{this}}></td>

… if you’re trying to do what I think you’re trying to do.

Then, as long as listParams and dataItems are helpers that both return data from the same reactive data source, you should be okay.

The helpers would be something like:

Template.my_table.helpers({
  listParams: function () {
    var doc = ParamsForTables.findOne({_id: Session.get('currentTableId')});
    return doc.listParams.sort(); // assuming `listParams` is an array of strings, which are the headings of your columns
  },
  dataItems: function () {
    var doc = ParamsForTables.findOne({_id: Session.get('currentTableId')});
    var columns = doc.listParams.sort(); // note that the columns for the data items are sorted the same way as the headings
    var dataItems = DataItems.find({table_id: Session.get('currentTableId')});
    return _.map(dataItems, function (dataItem) {
      // create a temporary field called `checkboxes` for each row with a set of Booleans that match the checked state of the columns
      // assuming here that for each row (data item document) there is a nested object called `checkedColumns` that maps the column name to a Boolean
      var checkboxes = _.map(columns, function (column) {
        return {
          column: column,
          checked: dataItem.checkedColumns[column],
          dataItemId: dataItem._id // this just makes it easier when capturing the event and making changes
        }
      });
      // extend the data item document with the temporary field and return it
      return _.extend(dataItem, {checkboxes: checkboxes};
    });
  }
});

You’d then need to capture the change events on the checkboxes and modify the particular document from the dataItems collection.

Template.my_table.events({
  'change input[type=checkbox]' : function () {
    var modifier = {};
    modifier['checkedColumns.' + this.column] = !this.checked;
    DataItems.update({_id: this.dataItemId}, {$set: modifier});
  }
});

This is an inelegant, inefficient solution, based on a whole lot of assumptions I’ve made about your data model, but it should work. (It’s all off the top of my head, so no guarantees.)

Just saw @herteby’s solution pop up. Much more elegant and understandable. I should have gone with something like that. :stuck_out_tongue:

Thanks you are a hero! It works. Just as a refinement, is it possible to have the value of the data appear only in the first column vertically, and leave the first row blank instead? Now, it appears on every cell, see the screenshot below.

21

Ideally what I would like to have is this:

11

If this is a big bother, having the key on the first column (as above) and still get the values in every cell is okay.

BTW, I amend <td>{{item[field]}} <input type="checkbox" value={{item[field]}}></td> and got no complains when run the app. I reckon there is no need for another helper right?

Thanks a lot, I agree @herteby 's solution saves me lots of code. I would take a look on the event though you pointed kindly, as this is my next step. Should this work combined with @herteby 's snippet?

No, it wouldn’t work without some modifications. The two approaches assume different data models. I think @herteby’s solution is making the right assumptions if it’s working for you right away like that! :slight_smile:

1 Like

Assuming an item looks like this:

{label:"pt 3.59408", amp:true, freq:true, mod:true}

You could do:

<table class="responsive-table-input-matrix">
  <thead>
    <tr>
      <th></th>
      {{#each field in listParams}}
        <th>{{field}}</th>
      {{/each}}
    </tr>
  </thead>
    
  <tbody>
    {{#each item in dataItems}}
      <tr>
        <th>{{item.label}}</th>
        {{#each field in listParams}}
          <td><input type="checkbox" value={{getField item field}}></td>
        {{/each}}
      </tr>
   {{/each}}
  </tbody>
</table>

And this template helper:

getField(item, field){
  return item[field]
}

(I think that should work, haven’t used “mustache language” in a while)

1 Like

It says: “Error: {{#each}} currently only accepts arrays, cursors or falsey values.”

My dataItems helper is this:

'dataItems': function(){
            return Session.get('selected');
}

And item looks like this:
{"pt":0.1,"eta":0.3,"phi":0.7,"charge":3}

Selecting an item is through this event:

Template.ipsosboard.events({
        'change #category-select': function(event, template) {
            var selected = $( event.currentTarget ).val();
            Session.set('selected', selected);
            console.log(selected);
        }
});

Returning back to the table, I would like to be able to make an array or dictionary with the items that are mapped to each other when checking the box on the table. So I was wondering how to make the event look like.

I am trying to use something like this event, but it won’t work as expected. Ofcourse as you say there must be some edits to make it work with @herteby’s solution. Could anyone point a nice way to get the mapped items of the table, ideally I would be able to have an object, or a dictionary or array providing the bind items. I would really appreciate some help as I am stuck with this and deadline is approaching.

Hey guys, sorry to bring this up again, but I really want to make this work as deadline is approaching. So I have so far this which is not entirely successful. What I am missing is that my array doesn’t provide a combination of the data-parameters in the array but just the values of the checkboxes, ofcourse when I mark the corresponding checkbox. Could there be any solution with this below, as I said I would really love to make this work so any help will be much appreciated.

 <table id="matrix-input" class="responsive-table-input-matrix">
		<thead>
        <tr>
            {{#each field in listParams}}
			      <th>{{field}}</th>
            {{/each}}
        </tr>
		</thead>

    <tbody>
        {{#each item in dataItems}}
		    <tr>
                   			{{#each field in listParams}}
			      <td>{{item[field]}} <input type="radio" value={{item[field]}}></td>
            {{/each}}
        </tr>
        {{/each}}
    </tbody>
	     </table>
 'click #matrix-input': function(event, template){
            //event.preventDefault();
            var selected = template.findAll("input[type=checkbox]:checked");
            var array = _.map(selected, function(item){
                return item.defaultValue;
            });
            console.log(array);
        }

You’ve either got to traverse up the table to the thead and locate the correct th for the column you’re in. Which sounds like way too much work.

Or, adopt a strategy something like this: when you write the table, include an appropriate class or data- attribute with each cell, which matches up with the relevant header. Then the header name is effectively bound to the cell and can be included when you process the checkbox.

I lost you there, I haven’t done this before so I am not sure how to make it. Would be a big bother to give some example?

Instead of writing your rows like this:

<td>{{item[field]}} <input type="checkbox" value={{item[field]}}></td>

write them like this:

<td class="{{field}}">{{item[field]}} <input type="checkbox" value={{item[field]}}></td>

and use $(event.target).attr('class') in your Template.ipsosboard.events to get the class: which will be your field name.

Edit: you may have to put that class="{{field}}" on the input instead. Just leaving for the day, so I’m out of time!