【问题标题】:Backbone.js read-only collection view renderingBackbone.js 只读集合视图渲染
【发布时间】:2012-06-28 20:27:51
【问题描述】:

我的问题的快速总结:视图的第一次渲染包含集合中的元素,而第二次渲染不包含。请继续阅读以了解详细信息...

另外值得注意的是:我确实意识到以下代码代表了幸福的道路,并且有很多潜在的错误处理代码丢失,等等,等等......这是为了简洁而故意的。

我对backbone.js 还是很陌生,在找出正确的方法来为以下场景实施解决方案时遇到了一些麻烦:

我有一个带有两个主要区域的页面,用于包含/显示将成为应用程序主要布局的渲染内容。一般来说,它看起来像这样:

-------------------------------------------
|                          |              |
|                          |              |
|         Content          |   Actions    |
|          Panel           |    Panel     |
|                          |              |
|                          |              |
|                          |              |
|                          |              |
|                          |              |
|                          |              |
-------------------------------------------

我有一个从中获取数据的 HTTP API,它实际上以 JSON 结果的形式提供各种模块的资源信息,这些信息来自对每个模块的基本 URL 的 OPTIONS 调用。例如,对http://my/api/donor-corpsOPTIONS 请求返回:

[
    {
        "Id":0,
        "Name":"GetDonorCorps",
        "Label":"Get Donor Corps",
        "HelpText":"Returns a list of Donor Corps",
        "HttpMethod":"GET",
        "Url":"https://my/api/donor-corps",
        "MimeType":"application/json",
        "Resources":null
    }
]

该应用程序有一个我称之为DonorCorpsCollection 的主干集合,它将是DonorCorpModel 对象的只读集合,可能由多个不同的主干视图呈现并以不同的方式显示在不同页面上应用程序(最终......后来......目前情况并非如此)。 DonorCorpsCollectionurl 属性需要是具有“GetDonorCorps”Name 属性的对象的 Url 属性,该属性是初始 OPTIONS 调用的结果,以获取 API 模块资源(如上所示)。

该应用程序的顶部有一个菜单栏,其中包含链接,单击该链接将呈现应用程序的各个页面。目前,应用程序中只有两个页面,“主页”页面和“预排序”页面。在“主页”视图中,两个面板中都只有占位符信息,指示用户应单击菜单栏上的链接来选择要执行的操作。当用户单击“预排序”页面链接时,我只想显示一个主干视图,该视图将DonorCorpsCollection 呈现为操作面板中的无序列表。

我目前正在使用JavaScript module pattern 来组织和封装我的应用程序代码,我的模块目前看起来像这样:

var myModule = (function () {

// Models
    var DonorCorpModel = Backbone.Model.extend({ });

// Collections
    var DonorCorpsCollection = Backbone.Collection.extend({ model : DonorCorpModel });

// Views
    var DonorCorpsListView = Backbone.View.extend({
        initialize : function () {
            _.bindAll(this, 'render');
            this.template = _.template($('#pre-sort-actions-template').html());
            this.collection.bind('reset', this.render); // the collection is read-only and should never change, is this even necessary??
        },

        render : function () {
            $(this.el).html(this.template({})); // not even exactly sure why, but this doesn't feel quite right

            this.collection.each(function (donorCorp) {
                var donorCorpBinView = new DonorCorpBinView({
                    model : donorCorp,
                    list : this.collection
                });

                this.$('.donor-corp-bins').append(donorCorpBinView.render().el);
            });

            return this;            
        }
    });

    var DonorCorpListItemView = Backbone.View.extend({
        tagName : 'li',
        className : 'donor-corp-bin',

        initialize : function () {
            _.bindAll(this, 'render');
            this.template = _.template($('#pre-sort-actions-donor-corp-bin-view-template').html());
            this.collection.bind('reset', this.render);
        },

        render : function () {
            var content = this.template(this.model.toJSON());
            $(this.el).html(content);
            return this;
        }
    });

// Routing
    var App = Backbone.Router.extend({
        routes : {
            '' : 'home',
            'pre-sort', 'preSort'
        },

        initialize : function () { },

        home : function () {
            // ...
        },

        preSort : function () {
            // what should this look like??
            // I currently have something like...

            if (donorCorps.length < 1) {
                donorCorps.url = _.find(donorCorpResources, function (dc) { return dc.Name === "GetDonorCorps"; }).Url;
                donorCorps.fetch();
            }

            $('#action-panel').empty().append(donorCorpsList.render().el);
        }
    })

// Private members
    var donorCorpResources;
    var donorCorps = new DonorCorpsCollection();
    var donorCorpsList = new DonorCorpsListView({ collection : donorCorps });

// Public operations
    return {
        Init: function () { return init(); }
    };

// Private operations
    function init () {
        getAppResources();
    }

    function getAppResources () {
        $.ajax({
            url: apiUrl + '/donor-corps',
            type: 'OPTIONS',
            contentType: 'application/json; charset=utf-8',
            success: function (results) {
                donorCorpResources = results;
            }
        });
    }

}(myModule || {}));

最后,这一切都使用了以下 HTML:

...
<div class="row-fluid">
    <div id="viewer" class="span8">
    </div>
    <div id="action-panel" class="span4">
    </div>
</div>
...
<script id="pre-sort-actions-template" type="text/template">
    <h2>Donor Corps</h2>
    <ul class="donor-corp-bins"></ul>
</script>

<script id="pre-sort-actions-donor-corp-bin-view-template" type="text/template">
    <div class="name"><%= Name %></div>
</script>

<script>
    $(function () {
        myModule.Init();
    });
</script>
...

到目前为止,我已经能够在我单击“预排序”菜单链接时第一次使用它。当我单击它时,它会按预期呈现捐赠者军团的列表。但是,如果我然后单击“主页”链接,然后再次单击“预排序”链接,这次我会看到标题,但 &lt;ul class="donor-corp-bins"&gt;&lt;/ul&gt; 是空的,其中没有列表项。 ...而且我不知道为什么或我需要做不同的事情。我觉得我一般理解backbone.js 视图和路由器,但在实践中,我显然遗漏了一些东西。

总的来说,这似乎是一个相当简单的场景,我不觉得我在这里尝试做任何特殊的事情,但我无法让它正常工作,而且似乎无法弄清楚出为什么。非常感谢您的协助,或至少向正确方向轻推。

【问题讨论】:

    标签: backbone.js backbone-views backbone-routing


    【解决方案1】:

    所以,我在这里找到了解决方案。不确定它是 最好的 还是正确的解决方案,但它至少目前有效。

    似乎有几个问题。根据 Rob Conery 的反馈,我开始研究使用我的视图模板渲染集合的不同选项,这就是我想出的:

    正如我在评论中提到的,我们使用Handlebars 进行视图模板,所以这段代码使用它。

    我最终将视图模板更改为如下所示:

    <script id="pre-sort-actions-template" type="text/x-handlebars-template">
        <h2>Donor Corps</h2>
        <ul class="donor-corp-bins">
        {{#each list-items}}
            <li class="donor-corp-bin">{{this.Name}}</li>
        {{/each}}
        </ul>
    </script>
    

    然后,我完全删除了DonorCorpListItemView,并将DonorCorpsListView 更改为现在的样子:

    var DonorCorpsListView = Backbone.View.extend({
        initialize : function () {
            _.bindAll(this, 'render');
            this.collection.bind('reset', this.render);
        },
        render : function () {
            var data = { 'list-items' : this.collection.toJSON() };
            var template = Handlebars.compile($('#pre-sort-actions-template').html());
            var content = template(data);
            $(this.el).html(content);
    
            return this;
        }
    });
    

    所以,这似乎正在工作,并且正在做我现在需要做的事情。

    关于 JS 和 Backbone.js 的工作原理,我显然还有很多需要了解,因为 以下内容不工作(引发错误)......并且我不知道为什么:

    var DonorCorpsListView = Backbone.View.extend({
        initialize : function () {
            _.bindAll(this, 'render');
            this.template = Handlebars.compile($('#pre-sort-actions-template').html());
            this.collection.bind('reset', this.render);
        },
        render : function () {
            var data = { 'list-items' : this.collection.toJSON() };
            var content = this.template(data);
            $(this.el).html(content);
    
            return this;
        }
    });
    

    希望这对某人有所帮助。

    【讨论】:

      【解决方案2】:

      对于 DonorCorpListView,您的渲染代码对我来说看起来很奇怪 - 看起来您在获得任何数据之前就在进行模板化。您需要编译该模板 with 数据以获取 HTML 输出。

      所以,渲染逻辑应该是这样的:

      var template = $([get the template).html();
      template.compile(this.collection.toJSON());
      this.el.append(template.render());
      

      我在这里使用了一些伪代码,因为我不确定你的模板机制是什么(我自己喜欢 Handlebars)。

      【讨论】:

      • 我只是在这个例子中使用 underscore.js 模板......但我使用 Handlebars 来实现。为了让它发挥作用,我想尽可能多地去除变量。
      • 不过,我想我明白你现在在说什么了...使用 Handlebars,我可以在集合 JSON 上使用 #each 帮助器,而不是遍历集合并为每个附加子视图物品。我可以试一试,但我不确定这是否能解决问题。不过感谢您的想法...
      猜你喜欢
      • 2016-05-19
      • 2011-12-19
      • 2013-10-21
      • 2013-03-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多