Search code examples
ember.jsember-data

Ember server side pagination


I'm not trying to provide pagination within the view itself.

My API returns 500 records at a time and if there are more I'd like to automatically load them.

Although my solution right now does make the requests, I don't think it is the best way, but it does work.

App.StructureAdapter = App.ApplicationAdapter.extend({
  findHasMany: function(store, record, url) {
    // based on the normal `findHasMany` code
    var host = Em.get(this, 'host'),
        id   = Em.get(record, 'id'),
        type = record.constructor.typeKey;

    if (host && url.charAt(0) === '/' && url.charAt(1) !== '/') {
      url = host + url;
    }

    return this.findWithURL(this.urlPrefix(url, this.buildURL(type, id)), 1);
  },
  findWithURL: function(url, page) {
    var that = this;
    var completeUrl = url + "?page=" + page;
    var nextPage = page + 1;
    return this.ajax(completeUrl, 'GET').then(function(data) {
      Em.Logger.log("calling then");
      if (data.structures.length > 0){
        that.findWithURL(url, nextPage);
      }
      return data;
    });
  }
});

My questions are:

  • Is there a better way to automatically get all of the pages for a given request?
  • How do I properly make sure the relationships are built. My Structure object has parent/children relationships on it, but only the first page of results is actually being associated correctly.

Update

Here is what my json response looks like:

{
    "structures": [
        {
            "id": 6536,
            "name": "Building",
            "updated_at": "2013-05-21T07:14:54-06:00",
            "person_id": 6535,
            "notes": ""
        },
        ... 499 more objects ...
    ]
}

It works properly, it loads the first group just fine. And I can adjust it in the extract/normalize methods if I need to.

Here is my normalize method as it is right now:

App.StructureSerializer = App.ApplicationSerializer.extend({
  normalize: function(type, hash, prop) {
    // adds the properly link to get children
    hash.links = { "children": "structures" };

    // change structure_id to parent_id
    hash.parent_id = hash.structure_id;
    delete hash.structure_id;

    return this._super(type, hash, prop);
  },
});

Again, the links makes it automatically know where to look for the has many relationship.

Looking at it closer, though the paginated pages actually do get called, they are not loaded into Ember data at all. So maybe if they did get loaded then the relationships would build properly.


Solution

  • Here's the best idea I have, I dunno how well it'd work and you might need to play around with it a bit.

    In your StructureRoute, go ahead and return the model as normal, so:

    App.StructureRoute = Ember.Route.extend({
        model:function() {
            return this.store.find('structure');
        }
    });
    

    That'll fetch your first 500 objects and begin the route transition.

    Then in your StructureController, fetch the other models using query parameters like this:

    App.StructureController = Ember.ArrayController.extend({
        init:function() {
            this.loadNextPage(2);
            this._super();  // this may not be necessary still, but the docs call for it
        },
        loadNextPage: function(page) {
            var self = this;
            var promise = this.store.find('structure',{page:page});
    
            promise.then(function(structures) {
                if(structures.get('length') < 500) { 
                    self.loadNextPage(page + 1);
                }
            });
        }
    });
    

    So when the StructureController initiates, it'll call the recursive function loadNextPage. This will keep running until it hits a page contains less then 500 models. Hopefully, that'll be the last page. By providing the second parameter to find, Ember should trigger a request to /structure?page=2. Inversely, you could do all of this in the route, if you don't mind the slow load time.

    If at all possible, I would suggest modifying your API to add some pagination meta data to your request. Then you can use that metadata to control when to stop the recursive function. You can see how to handle metadata here.

    Finally, I'm not sure if that's a typo in your json, but you may need to override your pluralization.

    Anywho, hope that helps and I didn't overly simply the problem!