无限滚动与Ember.js(懒加载)

55

我有一个视图,用户需要滚动查看大量内容,我想实现无限滚动以实现内容的逐步加载。

看起来有些人已经进行了分页,但谷歌上没有人讨论他们如何使用Ember/Ember Data实现无限列表。是否已经有人解决了这个问题并有博客文章/示例代码可分享?


1
非常好的问题,我希望你能得到答案,因为在这里我完全不知道如何做这件事,而且我很确定我会需要它。 - sly7_7
无限滚动的概念似乎相当简单,只需要分页数据,将其附加到容器中(例如一个带有多个附加li元素的ul),而不是从UI中删除现有内容并用您从存储中获取的内容替换它(通常是表格数据视图)。但可能涉及其他方面(如缓存等)。我想看一个例子,因为我目前没有时间亲自尝试编码。 - MilkyWayJoe
对此问题也很感兴趣,特别是关于存储在缓存中但尚未显示的数据量应该是多少(@MilkyWayJoe所提到的缓存)。另外,如果顶部结果发生了变化(例如推文被发布),如何最好地处理分页中的移位? - dechov
如果服务器端发生了触及“无限滚动”的事件 - 就像你提到的新推文已经被添加 - 应用程序应该使用其中一个始终连接的框架(例如node.js,signalr.js)在客户端上触发某些操作以加载更多结果。此外,客户端必须有一些监视页面滚动的东西 - 显然 - 以触发从服务器消耗数据的函数。 - MilkyWayJoe
4个回答

61

我在我正在开发的GitHub Dashboard项目中实现了一个无限滚动机制,该功能已添加到提交68d1728中。

基本思路是创建一个LoadMoreView,每次视图在当前视口上可见时调用控制器的loadMore方法。我使用jQuery插件inview来实现这一点。它允许您注册一个inview事件,在指定选择器的元素在屏幕上可见时触发,并在其消失时触发。

控制器还有一些属性,指示是否有更多要加载的项目,以及当前是否已获取项目。这些属性被称为canLoadMoreisLoading

LoadMoreView基本上如下所示:

App.LoadMoreView = Ember.View.extend({
  templateName: 'loadMore',
  didInsertElement: function() {
    var view = this;
    this.$().bind('inview', function(event, isInView, visiblePartX, visiblePartY) {
      if (isInView) Ember.tryInvoke(view.get('controller'), 'loadMore');
    });
  }
});

其中loadMore模板定义如下:

{{#if isLoading}}
    fetching some more stuff <img width="10" src="img/ajax-loader.gif" >
{{else}}
    {{#if canLoadMore}}
        <a {{action "loadMore" target="controller" }}>click to load more items</a>
    {{else}}
        <i>no more items</i>
    {{/if}}
{{/if}}

接下来实现处理获取更多项的控制器。请注意,在loadMore方法中,对存储进行了查询,它将为模型加载特定页面的条目。

App.EventsController = Ember.ArrayController.extend({
  currentPage: 1,

  canLoadMore: function() {
    // can we load more entries? In this example only 10 pages are possible to fetch ...
    return this.get('currentPage') < 10;
  }.property('currentPage'),

  loadMore: function() {
    if (this.get('canLoadMore')) {
      this.set('isLoading', true);
      var page = this.incrementProperty('currentPage');

      // findQuery triggers somehing like /events?page=6 and this
      // will load more models of type App.Event into the store
      this.get('store').findQuery(App.Event, { page: page });
    } else {
      this.set('isLoading', false);
    }
  }
});

唯一剩下的就是最初将控制器的content设置为filter函数的结果,这样当新模型加载到存储器中时(由于控制器中的loadMore方法中的findQuery),content会更新。此外,在调用filter时添加了一个query哈希值,以确保向服务器发出初始查询。

App.eventsController = App.EventsController.create({
    content: []
});

var events = App.store.filter(App.Event, { page: 1 }, function(data) {
    // show all events; return false if a specific model - for example a specific
    // type of event - shall not be included
    return true;
});

3
非常有帮助,但是为什么要使用filter呢?因为您实际上没有过滤任何内容(即return true),所以使用filter是否还有其他好处呢? - Alexander Wallace Matchneer
你为什么要使用 Ember.tryInvoke - Jakub Arnold
3
我不太明白最后一步 var event = ...。为什么要将其保存在变量中?变量在哪里使用,过滤器有什么用途? - polyclick
1
非常棒的回答。谢谢!我已经根据您的工作制作了一个版本,以便对其他人有所帮助。https://github.com/iHiD/meducation_mobile_app/commit/8bd955df461f2813de643cc47b9d8e032b1cec9c#commitcomment-2917505。 - iHiD
1
可以使用Ember.ViewTargetActionSupport mixin,而不是Ember.tryInvoke,然后在控制器中使用view.triggerAction {action: 'loadMore'}来触发操作。 在此处检查:http://emberjs.com/api/classes/Ember.ViewTargetActionSupport.html - Damian Walczak
显示剩余2条评论

15

这个能和Ember-Data一起使用并进行顺序页面查询吗? - Christopher Manning
可以在Ember.Data中使用def。我相信它会为您进行分页。请查看上面第二个链接,因为它有Erik Bryn的演示视频。 - commadelimited
该控件知道其基础内容的更新情况,如果更改,列表将会更新。看起来没有无限滚动行为。 - dmarr
据我所见,这个控件有几个需要解决的问题。首先,它是自己的窗体,有自己的滚动条 - 不适合像 Bootstrap 这样的框架。其次,Ember-Data 不提供可变数据集,因此在连接所有内容时需要更多的工作。 - dineth

5
我正在基于@pangratz的工作,为Ember编写一个无限分页插件
如果您有任何问题或想要改进,请在那里发布问题。

我为Ember js编写了一个无限滚动的mixin https://github.com/jeswinjose/Ember-Plugins/blob/master/infinite-scrolling-view.js - jsHero

1
我建议使用Ember Infinity插件。它支持Ember 1.10到2.0以上版本。它相对容易设置,你只需要修改你的路由和模板。
路由(Product是示例模型):
import InfinityRoute from 'ember-infinity/mixins/route';

export default Ember.Route.extend(InfinityRoute, {
  model() {
    /* Load pages of the Product Model, starting from page 1, in groups of 12. */
    return this.infinityModel('product', { perPage: 12, startingPage: 1 });
  }
});

模板:

{{#each model as |product|}}
  ...
{{/each}}

{{infinity-loader infinityModel=model}}

{{infinity-loader}} 组件变为可见时,它会向您的路由发送一个操作,以便知道要使用新(获取的)记录更新模型数组。
第一次请求将被发送到:
/products?per_page=12&page=1

所以您还需要准备后端API来处理这些查询参数。它显然是可定制的,请查看Readme的高级用法部分
注意:
使用ListView(@commadelimited的答案)和具有ArrayController的视图(@pangratz的答案)都已被弃用/删除,因为Ember 2.0已成为稳定版本。

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