在哪一点上获取视图所依赖的Backbone模型?

时间:2015-11-17 03:56:35

标签: javascript backbone.js client-side-scripting

我似乎经常在我渲染视图的情况下结束,但该视图所依赖的模型尚未加载。大多数情况下,我只有从网址中获取的模型ID,例如对于假设的市场应用程序,用户使用该URL登陆应用程序:

http://example.org/#/products/product0

在我的ProductView中,我创建了一个ProductModel并设置了其ID product0,然后我fetch()。我用占位符渲染一次,当获取完成时,我重新渲染。但我希望有更好的方法。

在呈现任何内容之前等待加载模型感觉没有响应。重新渲染会导致闪烁,并添加"正在加载...请等待"或者到处都是旋转器使得视图模板非常复杂(特别是如果模型提取失败,因为模型不存在,或者用户没有被授权查看页面)。

那么,当您还没有该模型时,呈现视图的正确方法是什么?

我需要离开吗? 来自hashtag-views并使用pushState?服务器能帮我推吗?我全都耳朵。

从已加载的页面加载:

我觉得,当已经加载页面而不是直接登陆Product页面时,您可以做更多事情。

如果应用程序呈现指向“产品”页面的链接,例如通过呈现ProductOrder集合,是否还可以执行其他操作?

<ul id="product-order-list">
  <li>Ordered 5 days ago. Product 0 <a href="#/products/product0">(see details)</a></li>
  <li>Ordered 1 month ago. Product 1 <a href="#/products/product1">(see details)</a></li>
</ul>

我处理这种链接到详细信息页面模式的自然方式是定义一条沿着这些方向做某事的路线:

routes: {
  'products/:productid': 'showProduct'
  ...
}

showProduct: function (productid) {
  var model = new Product({_id: productid});
  var view = new ProductView({model: model});
  //just jam it in there -- for brevity
  $("#main").html(view.render().el);
}

我倾向于在视图的fetch()函数中调用initialize,并从this.render()事件侦听器调用this.listenTo('change', ...)。这会导致复杂的render()个案例,并且对象会从视图中显示和消失。例如,我Product的视图模板可能会为用户评论保留一些屏幕空间,但是当且仅当产品上存在/启用评论时 - 并且在模型完全未知之前通常不知道从服务器获取。

现在,哪里/何时最好进行获取?

  1. 如果我在页面转换之前加载模型,则会导致直观的视图代码,但会引入用户可察觉的延迟。用户将单击列表中的项目,并且必须等待(不更改页面)才能返回模型。响应时间很重要,我还没有对此进行可用性研究,但我认为用户习惯于在点击链接后立即看到页面发生变化。

  2. 如果我将模型加载到ProductView initializethis.model.fetch(),并监听模型事件,我将被迫渲染两次, - 曾经有空占位符(因为否则你必须盯着白页),并且一次之后。如果在加载过程中发生错误,那么我必须擦除视图(显示闪烁/毛刺)并显示一些错误。

  3. 我还没有看到另一个选项,可能涉及可以在视图之间重用的过渡加载页面吗?或者总是第一次调用render()显示一些微调器/加载指示器是好的做法?

    修改:通过collection.fetch()

    加载

    有人可能会建议,因为这些项已经是列出的集合的一部分(用于呈现链接列表的集合),所以可以在单击链接之前使用collection.fetch()获取它们。如果集合确实是Product的集合,那么渲染产品视图会很容易。

    但用于生成列表的Collection可能不是ProductCollection。它可能是ProductOrderCollection或其他只是引用产品ID(或者提供足够数量的产品信息以呈现链接)的其他内容。

    如果Product模型很大,尤其是通过collection.fetch()获取所有Product也可能会过高。在点击其中一个产品链接的可能性。

    鸡肉还是鸡蛋?对于直接导航到产品页面的用户,collection.fetch()方法也无法真正解决问题...在这种情况下,我们仍需要呈现需要{{1}的ProductView页面只从id(或产品页面URL中的任何内容)中获取模型。

2 个答案:

答案 0 :(得分:3)

好吧,所以在我看来,有很多方法可以解决这个问题。我将列出我所想到的所有内容,并希望有人能与您合作,或者至少它会激励您找到最佳解决方案。

我并不完全反对T J的回答。如果您只是继续在网站加载时对所有产品执行collection.fetch()(用户通常希望有一些加载时间),那么您拥有所有数据,您可以将这些数据传递到他提到了。他的建议和我通常做的事情之间的唯一区别是我通常在我的所有观点中都有对app的引用。所以,例如在 app.js 中的初始化函数中,我会做类似的事情。

initialize: function() {
    var options = {app: this}

    var views = {
        productsView: new ProductsView(options)
    };

    this.collections = {
        products: new Products()
    }

    // This session model is just a sandbox object that I use
    // to store information about the user's session. I would
    // typically store things like currentlySelectedProductId
    // or lastViewedProduct or things like that. Then, I just
    // hang it off the app for ease of access.
    this.models = {
        session: new Session()
    }
}

然后在我的 productsView.js初始化函数中,我会这样做:

initialize: function(options) {
    this.app = options.app;

    this.views = {
        headerView: new HeaderView(options),
        productsListView: new ProductsListView(options),
        footerView: new FooterView(options)
    }; 
}

我在productsView.js中初始化时创建的子视图是任意的。我主要是试图证明我继续将该选项对象传递给视图的子视图。

这样做允许每个视图,无论是顶级视图还是深层嵌套子视图,每个视图都知道每个其他视图,每个视图都引用了应用程序数据。

这两个代码示例还介绍了尽可能精确地确定功能范围的概念。不要尝试拥有能够完成所有事情的观点。将功能传递给其他视图,以便每个视图都有一个特定目的。这也将促进视图的重用。特别复杂的模态。

现在回到手头的实际话题。如果你打算继续加载所有产品,你应该在哪里取出它们?因为就像你说的那样,你不想只是坐在你面前的空白页面。所以,我的建议是欺骗他们。尽可能多地加载页面,只阻止需要加载数据的部分。对于用户而言,当您实际在幕后工作时,页面看起来就像加载一样。如果你可以欺骗用户认为页面正在稳定加载,那么他们就不太可能对页面加载感到不耐烦。

因此,引用productsView.js的初始化,你可以继续让headerView和footerView呈现。然后,您可以在productsListView的渲染中进行提取。

现在,我知道你在想什么。我迷失了自己的想法。如果你在渲染函数中进行提取,那么在我们点击实际呈现productsViewList模板的行之前,调用没有时间返回。嗯,幸运的是,有几种方法可以解决这个问题。一种方法是使用Promises。但是,我通常这样做的方法是只使用render函数作为自己的回调。让我告诉你。

render: function(everythingLoaded) {
    var _this = this;

    if(!!everythingLoaded) {
        this.$el.html(_.template(this.template(this)));
    }
    else {
        // load a spinner template here if you want a spinner

        this.app.collection.products.fetch()
            .done(function(data) {
                _this.render(true);
            })
            .fail(function(data) {
                console.warn('Error: ' + data.status);
            });
    }

    return this;
}

现在,通过以这种方式构建渲染,实际模板不会加载,直到数据完全加载。

虽然我们在这里有一个渲染功能,但我想引入另一个我在每个地方使用的概念。我称之为postRender。这是一个函数,我在模板加载完成后执行任何依赖于DOM元素的代码。如果您只是编写一个简单的.html页面,那么这是传统上在 $(document).ready(function(){}); 中的代码。值得注意的是,我不会将.html文件用于我的模板。我使用嵌入式javascript文件(.ejs)。继续,postRender功能是我基本上添加到我的锅炉板代码的功能。所以,每当我在代码库中为一个视图调用render时,我立即将postRender链接到它上面。我也像使用渲染一样使用postRender作为回调。因此,基本上以前的代码示例在我的代码库中看起来就像这样。

render: function(everythingLoaded) {
    var _this = this;

    if(!!everythingLoaded) {
        this.$el.html(_.template(this.template(this)));
    }
    else {
        // load a spinner template here if you want a spinner

        this.app.collection.products.fetch()
            .done(function(data) {
                _this.render(true).postRender(true);
            })
            .fail(function(data) {
                console.warn('Error: ' + data.status);
            });
    }

    return this;
},

postRender: function(everythingLoaded) {
    if(!!everythingLoaded) {
        // do any DOM manipulation to the products list after 
        // it's loaded and rendered
    }
    else {
        // put code to start spinner
    }

    return this;
}

通过链接这些函数,我们保证它们会按顺序运行。

=============================================== ==========================

所以,这是解决问题的唯一方法。但是,您提到您不希望必须预先加载所有产品,因为担心请求可能需要很长时间。

旁注:您应该考虑取出与产品调用相关的任何可能导致调用花费大量时间的信息,并将更大的信息作为单独的请求。我觉得如果能够真正快速地获取核心信息,并且如果与每个产品相关的缩略图需要更长时间才能加载它,那么用户将会更加宽容数据需要一段时间才能加载它不应该结束世界的。这只是我的意见。

解决此问题的另一种方法是,如果您只想转到特定产品页面,那么只需在单个productView上实现我在上面列出的 render / postRender模式。但请注意,您的 productView.js 可能需要看起来像这样:

initialize: function(options) {
    this.app = options.app;
    this.productId = options.productId;

    this.views = {
        headerView: new HeaderView(options),
        productsListView: new ProductsListView(options),
        footerView: new FooterView(options)
    }; 
}

render: function(everythingLoaded) {
    var _this = this;

    if(!!everythingLoaded) {
        this.$el.html(_.template(this.template(this)));
    }
    else {
        // load a spinner template here if you want a spinner

        this.app.collection.products.get(this.productId).fetch()
            .done(function(data) {
                _this.render(true).postRender(true);
            })
            .fail(function(data) {
                console.warn('Error: ' + data.status);
            });
    }

    return this;
},

postRender: function(everythingLoaded) {
    if(!!everythingLoaded) {
        // do any DOM manipulation to the product after it's 
        // loaded and rendered
    }
    else {
        // put code to start spinner
    }

    return this;
}

唯一的区别是productId在options对象中传递给初始化,然后在渲染函数的.fetch中拉出并使用它。

=============================================== ========================== 总之,我希望这会有所帮助。我不确定我是否回答了你所有的问题,但我认为我对他们做了很好的回答。为了这个时间过长,我现在要停在这里,让你消化这个问题并提出任何问题。我想我可能不得不至少对这篇文章做一次更新,以便进一步刷新它。

答案 1 :(得分:2)

你开始说:

  

我在一个Collection视图中有一个项目列表

那么集合有什么......?模型..!

执行collection.fetch()时,您将检索所有模型。

当用户选择一个项目时,只需将相应的模型传递给项目视图,例如:

this.currentView = new ItemView({
  model: this.collection.find(id); // where this points to collection view
                            // and id is the id of clicked model
});

这样,就不会有任何延迟/不正确的渲染。

如果您的馆藏终点返回大量数据怎么办?

然后实现分页,延迟加载等常见做法。

  

我构建了一个具有给定ID

的Product模型

对我来说这听起来不对。如果您有一系列产品,则不应手动构建此类模型。

让集合在渲染列表视图之前获取模型。这样就可以避免你提到的所有问题。

相关问题