Search code examples
javascriptmongodbmeteorsubdocumentminimongo

Returning subdocument array through Meteor / Mongo


I'm having a little trouble returning and displaying tags that I'm adding to a subdocument. I have no problem adding tags, but want to put a label on the item for each tag. I simply can't find a resource that helps me return the items in an array within a subdocument. I think it's all the helper where I'm stuck - basically the syntactically correct way to write "Items.(this._id).itemTags.find();" :)

Oh - and I've cut out a lot of the HTML and JS, but, yes, everything else is working fine. The collection is "Items" and the subdocument is "itemTags", set with "itemTags: []" during the insert. In my test environment I can add "Cats" and "Dogs" as tags and can verify it works by inspecting the objects through "Items.find().fetch();" but am struggling to display them.

HTML:

<template name="item">
  {{#each itemTags}}
    <span class="label label-default">{{itemTag}}</span>
  {{/each}}
</template>

JS:

Template.item.helpers({
   itemTags: function() {
    var currentUserId = Meteor.userId();
    return Items.find(); // yes, this line is completely wrong, but I'm lost hehe
  }
});

Template.item.events({
  'submit .add-tag': function(event) {
    event.preventDefault();

    var itemTag = event.target.text.value;

    Items.update(this._id, {$push: {itemTags: itemTag}});

    event.target.text.value = "";

    return false;
  }
});

Database schema (as shown by my insert command):

var item = {
  itemText: $(e.target).find('[name=itemText]').val(),
  createdAt: new Date(),
  createdBy: currentUserId,
  hard: false,
  difficulty: 'easy',
  checked: false,
  itemTags: [],
};

item._id = Items.insert(item);

Solution

  • Your only problem is trying to iterate a cursor and a sub array within the same each block. If you separate your items template and an individual item template, you'll end up with your desired result.

    For the sake of simplification, I altered your code to look like this:

    This is your main body:

    <body>
        {{> items}}
    </body>
    
    <template name="items">
      {{#each items}}
        {{> item}}
      {{/each}}
    </template>
    
    <template name="item">
      <h2>{{itemText}} tags are:</h2>
      <ul>
      {{#each itemTags}}
        <li>{{this}}</li>
      {{/each}}
      </ul>
    </template>
    

    And this is your helper:

    Template.items.helpers({
      items: function () {
        return Items.find();
      }
    })
    

    Assuming an item document looks like:

    { 
      itemText: String,
      itemTags: Array
    }
    

    I've created an app on Meteorpad for you to play with:

    http://meteorpad.com/pad/BmRQ5fkwWEMBKszJW/SO-27951102

    you can further alter the code there and see the changes in realtime. It is basically jsfiddle for meteor.

    Edit: inspired by @chip-castle's comment, you can in fact use a single template with nested each blocks:

    <template name="items">
      {{#each items}}
        <h2>{{itemText}} tags are:</h2>
        <ul>
        {{#each itemTags}}
          <li>{{this}}</li>
        {{/each}}
        </ul>
      {{/each}}
    </template>
    

    But using separate templates is more flexible in both design and handling events where necessary.