骨干视图组织和局部视图

时间:2012-09-09 16:40:23

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

我有一个相当复杂的Backbone应用程序,我不知道如何组织视图/模板。该应用程序是基于Web的电子邮件客户端。我真的很难理解如何制作这个侧边栏。

应用程序侧栏与您在Apple Mail / Outlook中看到的非常相似。它基本上是一个文件夹浏览器。该视图存在于每个页面上。

我有两个主要问题:

如何将收集数据放入侧边栏视图?

技术上有三个“集合”可以在侧边栏上呈现 - accounts =>邮箱和标签。所以一些示例代码如下所示:

<div class="sidebar">
  <div class="sidebar-title">Mailboxes</div>

  <div class="sidebar-subtitle">Gmail</div>
  <div class="mailboxes" data-account-id="1">
    <ul>
      <li><a href="...">Inbox</a></li>
      <li><a href="...">Sent</a></li>
      ...
    </ul>
  </div>

  <div class="sidebar-subtitle">Yahoo</div>
  <div class="mailboxes" data-account-id="2">
    <ul>
      <li><a href="...">Inbox</a></li>
      <li><a href="...">Sent</a></li>
      ...
    </ul>
  </div>

  <div class="sidebar-title">Labels</div>
  <div class="sidebar-labels">
    <ul>
      <li>Home</li>
      <li>Todo</li>
    </ul>
  </div>
</div>

所以理论上我需要做这样的事情,不是吗?

<div class="sidebar">
  <div class="sidebar-title">Mailboxes</div>

  <% for account in @accounts.models: %>
    <div class="sidebar-subtitle"><%= account.get('name') %></div>
    <div class="mailboxes" data-account-id="<%= account.get('id') %>">
      <ul>
        <% for mailbox in account.mailboxes.models: %>
          <li><a href="..."><%= mailbox.get('name') %></a></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="sidebar-title">Labels</div>
  <div class="sidebar-labels">
    <ul>
      <% for label in @labels: %>
        <li><%= label.get('name') %></li>
      <% end %>
    </ul>
  </div>
</div>

问题是我无法从路由器传递@accounts@labels。我意识到我可以使用@options,但这看起来很混乱而且不健全。此侧栏需要存在于每个页面上。在Backbone堆栈上应该是什么样子?我不认为它应该有自己的路由器,因为它不是真正的“页面”。

我是否将其分解为较小的视图?

每个邮箱应该是自己的视图吗?每个邮箱应该是自己的视图吗?每个标签都有自己的看法?我想监听事件,例如新邮件,并更新邮箱未读计数(例如)。但是我该怎么做?如何嵌套视图并有效处理所有对象创建/获取?如果我要将事情分解为较小的视图,我的侧边栏视图应该是什么样的?

对不起,如果我在散步。我已经看了好几天了,似乎无法找到一个好的解决方案。

1 个答案:

答案 0 :(得分:6)

使用options完全合法且Backbone-y。所以我不会害怕使用它,尽管有很多其他方法可以解决这个问题。 是啊。我认为侧边栏应该在更大的页面视图中初始化,并由路由器调用。

我还认为您的邮箱应该是自己的子视图。听起来无论邮件是雅虎还是谷歌,每个邮箱都具有相同的功能(如最小化),因此创建该视图类并多次重复使用它会很有意义。

那么这个邮箱视图应该采取什么措施? (关于收集)

同样,你有选择。扔掉一个包含所有内容的大型集合,或者抛出代表某种帐户邮件的每个集合......

如果您的馆藏已经分开(例如1个用于Google邮件,1个用于Yahoo邮件),则可以轻松创建子视图并将相应的集合传递给每个子视图。即使它不是分开的,我可能会将集合过滤到它所需的模型,然后再将其作为集合传递。这就是我倾向于的东西。我假设在每个邮箱视图中都不会出现该邮箱需要访问任何与特定帐户无关的邮件模型的情况。所以传递你不需要的东西是没有意义的。

至于标签,我不确定。这取决于我认为你的标签是什么以及如何。例如,您是否拥有自己的标签模型,它基本上是与邮件model.id关联的标签标签?或者标签是您正在采摘的邮件模型的属性?

使用示例代码进行更新 - 专注于通过视图层次结构传递数据

所以我基本上在这里概述了在父视图中创建子视图以及如何通过视图层次结构传递不同数据(在您的案例集合中读取)的一种非常常见的方式。

您可以通过简单地重复模式,利用[options]的{​​{1}}参数,为更多的子视图(例如每个邮件模型可能将其自己的代表子视图作为邮箱视图中的列表项等)执行此操作。 View构造函数并传递内容并在内部接受它。

在这段代码的很多部分中,我采用了一种冗长的方法,以使其更加透明,确切地将数据传递到哪里。我略微更改了您的邮箱模板,只是为了说明如何添加更多子视图,但您当然可以将其更改回来。您对MailboxView的处理取决于您和您的代码(取决于您的目标)应该反映出来。

所以,不用多说,这里有一些示例代码可供选择和思考。我把它鞭打在一起所以可能会有错误。我实际上并没有看到它是否会执行。

通常在视图中,我们可能会将view.el定义为模板中最外层的元素,并且只包含内部部分,但要与您提供的模板保持一致,并减少可能令人困惑的额外内容的数量。代码,我大部分时间都保留了模板。

// Your templates
<script id="sidebar" type="text/template">

    <div class="sidebar">
    <div class="sidebar-title">Mailboxes</div>
        // Mailbox here
        // Mailbox here
        // Labels  here
    </div>

</script>

// I altered this template slightly just to illustrate a point on appending the model
// view data into your mailbox view. You might not want this but it's just for
// demonstration purposes
<script id="mailbox" type="text/template">
    <div class="sidebar-subtitle">Gmail</div>
    <div class="mailboxes" data-account-id="1">
        <ul class="inbox"></ul>  // Changed this
        <ul class="sent"></ul>
    </div>
</script>

<script id="labelBox" type="text/template">
    <div class="sidebar-title">Labels</div>
        <div class="sidebar-labels">
            <ul>
                <li>Home</li>
                <li>Todo</li>
            </ul>
        </div>
    </div>
</script>

您的应用程序BIG视图,​​包含所有内容的主视图。

AppView = Backbone.View.extend({
    initialize: function() {
        // You might not instantiate your collections here but it is a good starting
        // point for this demo. We'll be passing these through children view and 
        // utilizing them in the appropriate views.

        // Let's assume these collections we instantiate already have models inside them.

        // Your 3 Collections
        this.gmail = new MailCollection();
        this.yahoo = new MailCollection();
        this.labels = new LabelCollection();

    },
    render: function() {
        this.$el.html();

        // We pass each collection into the SidebarView [options] hash. We can "pick-up"
        // these collections in the sidebar view via this.options.xxx

        // Render the sidebar
        var sidebar = new SidebarView({
            'gmail':this.gmail,
            'yahoo':this.yahoo,
            'labels':this.labels
        });

        // Render the sidebar and place it in the AppView... anywhere really
        // but for simplicity I just append it to the AppView $el
        this.$el.append(sidebar.render().el);

        return this;
    }
});

您的侧边栏视图:

SidebarView = Backbone.View.extend({
    template: _.template($('#sidebar').html()),
    initialize: function() {

        // We passed the 3 collections into Sidebar view and can access
        // them through the options. We could have instantiated them here
        // but I passed them into the side to illustrate that using
        // options for this kind of thing is a-okay.

        this.gmail = this.options.gmail,
        this.yahoo = this.options.yahoo,
        this.labels = this.options.labels

        // This is an array of sub-view mailboxes so when we close this view,
        // it's easy to loop through the subviews and close both mailboxes
        this.mailboxes = [];
    },
    render: function() {
        // We render the sidebarView using the template
        this.$el.html(this.template());

        // We generate the sub-view mailbox views (gmail and yahoo)
        var gmailView = new MailboxView({
            'collection': this.gmail // We pass in only the gmail collection
        });
        var yahooView = new MailboxView({
            'collection': this.yahoo // Pass in the yahoo mail collection
        });
        var labelView = new LabelboxView({
            'collection': this.labels // Pass in the labels collection
        });

        // We push the views into our array
        this.mailboxes.push(gmailView);
        this.mailboxes.push(yahooView);
        this.mailboxes.push(labelView);

        // Render each view and attach it to this sidebar view
        this.$el.append(gmailView.render().el);
        this.$el.append(yahooView.render().el);
        this.$el.append(labelView.render().el);

        return this;
    },
    onClose: function() {
        // Sample code of how we close out child views. When this parent view closes,
        // it automatically cleans up the child views.

        _.each(this.mailboxes, function(view) {
            view.close(); // A special function that I use to close views
        });
    }
});

有关我使用的onClose()close()方法的详细信息,请参阅Zombie View Cleanup。一旦您开始在应用程序中创建大量视图/子视图关系,这将特别有用。

您的邮箱视图:

MailboxView = Backbone.View.extend({
    template: _.template($('#mailbox').html()),
    initialize: function() {
        // When we pass something in as 'collection' backbone automatically
        // attaches it as a property of the view so you can use the collection
        // as this.collection rather than this.options.collection for
        // convenience
    },
    render: function() {
        this.$el.html(this.template());

        this.loadMail();

        return this;
    },
    loadMail: function() {
        // Some code that loops through your collection and adds the mail to an
        // appropriate DOM element. Would make sense to make Inbox a ul and each
        // mail an li so I altered your template to demonstrate this

        // Let's assume a mail model has the attr 'author' and 'body' in this
        // simple example

        // This will loop through your collection and use a mail template to
        // populate your list with the appropriate data.
        this.collection.each(function(mail) {
            this.$('ul').append(_.template('<li><%=author%><%=body%></li>', mail.toJSON()))
        });

        // Alternatively, you could (and probably should) make each mail model
        // represented by ANOTHER sub-view, a mail subview that has all the
        // functionality that mail views usually have.
        // To accomplish this you just repeat the prior demonstrated cycle
        // of creating child view inside child view, passing in the appropriate
        // data that you need, creating the view, and attaching it to the parent
        // where you would like.
    }
});

您的LabelsView:

LabelboxView = Backbone.View.extend({
    template: _.template($('#labelBox').html()),
    initialize: function() {
        // I think you get the gist from above, you'd do the same thing as each
        // mailbox view except deal with the unique aspects of labels, whatever
        // these may be (and do)
    },
    render: function() {
        this.$el.html(this.template());
        return this;
    }
});