如何在Meteor中呈现嵌套集合?

4

概述:
Meteor模板中未渲染父类别中嵌套的子类别。

详情:
考虑一个“类别(Category)”的数据模型:

// Model Schema
Category {
   idCategory : 20, (id of the category itself)
   idCategoryParent : 0, (idCategory of our parent category)
   defaultLabel : "Movies" (our label)
}

有父级分类和子级分类。父级分类的idCategoryParent属性值为0。子级分类将其父级的idCategory作为其idCategoryParent属性存储。我正在尝试循环遍历这些分类的集合,并以以下方式呈现它们:

<b>Movies</b> // parent category is in bold
<ul> // child categories are rendered as an unordered list
  <li>Horror</li> 
  <li>Comedy</li>
  <li>Action</li>
  <li>Drama</li>
</ul>

<b>Music</b>
<ul>
  <li>Rock</li> 
  <li>Classical</li>
  <li>Ambient</li>
</ul>

然而,实际得到的是:
<b>Movies</b>
<ul> // empty...
</ul>

<b>Music</b>
<ul>
</ul>

源代码:

// How we get the 'categories_parents' data
Template.content.categories_parents = function (){ 

    /*
    * Get all parent categories (categories with an idCategoryParent of 0)
    */
    var parents = Categories.find({idCategoryParent:0});
    var pCount = parents.count();

    for (var i = 0; i < pCount; i++){

            var pId = parents.db_objects[i]['idCategory'];
            /* 
            * Get all child categories of the parent (categories with
            * an idCategoryParent equal to value of parent category's idCategory).
            */
            var children = Categories.find({idCategoryParent:pId});
            var cCount = children.count();

            /*
            * Assign the child categories array as a property of the parent category
            * so that we can access it easily in the template #each expression
            */
            parents.db_objects[i]['children'] = children;
    }

    return parents;
}


// This is our template
<template name="content">
<h1>Categories</h1>
    {{#each categories_parents}}
        <b>{{defaultLabel}}</b><br />
        <ul>
            {{#each children}}
            <li>{{defaultLabel}}</li>
            {{/each}}
        </ul>
    {{/each}}
</template>

我在故障排除时尝试的其他模板配置:

{{#each children}}
<li>A Child Exists Here</li> // Even this never rendered... no children?
{{/each}}

如果您能提供任何有关发生这种情况的线索,将不胜感激。

3个回答

15

你的模型有点不可靠...考虑一下

{name:"分类名称", parent:"父类别的_id"}

好的,这样简单多了。创建一个分类。

var moviesId = Categories.insert({name:"Movies"});
Categories.insert({name:"Horror",parent:moviesId});

这很容易。现在,以一种{{#each}}可以使用的方式呈现:

Template.categories.categories = function(parent) {
  if (parent) {
    return Categories.find({parent:parent}).fetch();
  } else {
    return Categories.find({parent:{$exists:false}});
  }
}

你可能已经看到了这个的发展趋势...

<template name="categories">
   {{#each categories}}
   <ul>{{name}}
       {{#each categories _id}}
          <li>{{name}}</li>
       {{/each}}
   </ul>
   {{/each}}
</template>

现在我不确定 {{#each}} 块助手是否可以在调用另一个助手时带有函数参数。如果它不能...

Template.categories.categories = function() {
  return Categories.find({parent:{$exists:false}}).map(function(parentCategory) {
    return _.extend(parentCategory,
                    {children:Categories.find({parent:parentCategory._id}).fetch()});
  }); 
}

那真是个棘手的问题。它返回父类别并带有一个新的“children”列表属性,其中包含所有子类别。现在你可以这样做:

<template name="categories">
   {{#each categories}}
   <ul>{{name}}
       {{#each children}}
          <li>{{name}}</li>
       {{/each}}
   </ul>
   {{/each}}
</template>

很聪明,对吧?


出色解决方案和巧妙的模型重构将得到积分。 - eric
map的作用是什么? - yozawiratama
“map”将获取每个具有子类别的类别并连接这些子类别。 - DoctorPangloss
这比将子类别存储为父文档内的对象数组更好吗? - user1447679
你确实可以向 {{#each}} 助手传递一个参数,因此第一个示例应该可以工作(在 Meteor 1.4 上测试过)。 - Sphinxxx
1
如何在多个深度级别上实现这一点,例如20个类别的深度? - Cos

0

我不了解db_objects,当我尝试在光标上访问该属性(这是find()返回的内容)时,它是null

您可以获取与查询匹配的项目,然后进行迭代:

Template.content.categories_parents = function (){ 

    var parents = Categories.find({idCategoryParent:0}).fetch(); // Returns an array.

    for (var i = 0; i < parents.length; i++){

        var pId = parents[i]['idCategory'];

        var children = Categories.find({idCategoryParent:pId});

        // No need for array here, cursor is fine.
        parents.db_objects[i]['children'] = children; 
    }

return parents;
}

我自己也是新手,所以可能有更高效的方法,但目前我还不知道。

在Eric的评论之后更新。

这个js文件看起来像这样:

Categories = new Meteor.Collection("categories");

if (Meteor.isClient) {
    Template.categories.categories = function () {
        var parents = Categories.find({idCategoryParent:0}).fetch();

        for (var i = 0; i < parents.length; i += 1) {
            var pId = parents[i]['idCategory'];

            var children = Categories.find({idCategoryParent:pId});

            parents[i]['children'] = children;

        }
        return parents;
    };
}

if (Meteor.isServer) {
    Meteor.startup(function () {
        Categories.remove({});
        var data = [
            {idCategoryParent: 0, idCategory: 1, label: "Movies"},
            {idCategoryParent: 1, idCategory: 0, label: "Science Fiction"},
            {idCategoryParent: 1, idCategory: 0, label: "Drama"},

            {idCategoryParent: 0, idCategory: 2, label: "Music"},
            {idCategoryParent: 2, idCategory: 0, label: "Jazz"},
            {idCategoryParent: 2, idCategory: 0, label: "Piano"}
          ];

        for (var i = 0; i < data.length; i += 1) {
            Categories.insert(data[i]);
        }
  });
}

HTML文件的外观如下:

<head>
  <title>nested_template</title>
</head>

<body>
  {{> categories}}
</body>

<template name="categories">
    <h1>Categories</h1>
    {{#each categories}}
        <b>{{label}}</b>
        <ul>
        {{#each children}}
            <li>{{label}}</li>
        {{/each}}
        </ul>
    {{/each}}
</template>

对我来说它运作得非常好。


谢谢你尝试了一下。不幸的是,它对我没有起作用。 - eric
我最初试图将“children”数组强制添加到父类别对象上,而您的代码允许我通过获取实际数组来实现这一点(我错误地尝试将子项强制添加到游标本身)。我将不得不测试您更新后的解决方案,并查看它的更新和同步如何工作(即当向系统添加新的子类别时,是否所有打开的选项卡都能正确实时更新)。 - eric

-1

问题已解决。

我的解决方案是从模板中删除{{#each}}逻辑,并用单个handlebars助手表达式替换它,然后一次性传回所需的html。该html是从类别集合的游标数据生成的。

不太确定将所有这些html放在我的逻辑中是否是不良设计?如果是,我会采纳更好的答案。

// This is the new template
<template name="content">
<h1>Categories</h1>
    {{listCategories}}
</template>



// Handlebars helper 
Handlebars.registerHelper('listCategories', function() {

  var parents = Categories.find({idCategoryParent:0});
  var countParents = parents.count();
  var string = '';

  // iterate over each parent category  
  for(m = 0; m < countParents; m++){

      // Get the parents category id
    var pId = parents.db_objects[m].idCategory;
    var children = Categories.find({idCategoryParent:pId});
    var count = children.count();

      /*
      * Build the Parent Category html
      * Example: <b>Movies</b><ul>
      */
    string = string + '<b>' + parents.db_objects[m].defaultLabel + '</b><ul>';

    // iterate over each child category
    for(var i = 0; i < count; i++){

         /*
         * Build the child category html
         * Example: <li>Horror</li>
         */
       string = string + '<li>' + children.db_objects[i]['defaultLabel'] + '</li>';
    }

      // Close up the unordered list
    string = string + '</ul>';

  }

  // Return the string as raw html
  return new Handlebars.SafeString(string);

});



// Rendered out the result correctly like so:
<b>Movies</b>
<ul>
  <li>Horror</li> 
  <li>Comedy</li>
  <li>Action</li>
  <li>Drama</li>
</ul>

<b>Music</b>
<ul>
  <li>Rock</li> 
  <li>Classical</li>
  <li>Ambient</li>
</ul>

我认为你不应该在JavaScript助手中创建HTML。这样做不够简洁。 - Björn Grossmann
@BjörnGrossmann 好的,那么你会把HTML放在哪里?为那些不知道的人发布另一个解决方案。 - eric

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接