Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure works with JSONAPI pagination requirements #97

Open
kellyselden opened this issue Oct 18, 2015 · 34 comments
Open

Ensure works with JSONAPI pagination requirements #97

kellyselden opened this issue Oct 18, 2015 · 34 comments

Comments

@kellyselden
Copy link
Collaborator

I'm a bit new to JSONAPI, but it looks like the meta key goes away and becomes the links key. I tried the new JSONAPIAdapter and doesn't look like this addon plays well with it. Perhaps we can first make the response format overridable, to make the user handle the links key themselves and transform it into something we expect. We could also sniff the adapter and try to pick the response format automatically.

Comments welcome because this is just the beginning of my thoughts.

@kellyselden
Copy link
Collaborator Author

Link for reference. It is fuzzy for me what the response looks like because there are no examples.

@hhff
Copy link
Collaborator

hhff commented Oct 20, 2015

Yeah was actually just thinking about this... The spree_api_v2 gem is all JSONAPI, and I'm looking to get it working with Spree Ember (and Ember Infinity!)

will think on it also!

@dpatz
Copy link

dpatz commented Oct 24, 2015

Agreed, would be awesome if Ember Infinity supported the new JSON API format. I'd be happy to help if you're interested @hhff.

@kellyselden
Copy link
Collaborator Author

@dpatz I think a first step would be to extract the response parsing into an overridable function if it isn't already. That would mean an already built server with a paging api could use ember-infinity if you override the response handling function. The function would take a raw response body and return a object we expect. This would make for an easy transition to JSON API.

You could start there if you have the time 😄

@hhff
Copy link
Collaborator

hhff commented Oct 26, 2015

👍

@davidgoli
Copy link
Collaborator

Seems like the model's serializer's extractMeta hook could be leveraged for
this, without doing any parsing in the add-on?
On Oct 26, 2015 9:04 AM, "Hugh Francis" notifications@github.com wrote:

[image: 👍]


Reply to this email directly or view it on GitHub
#97 (comment).

@dpatz
Copy link

dpatz commented Oct 26, 2015

Definitely agree with @davidgoli that the right place to do the parsing is in the extractMeta hook (until the serializer takes care of this for us).

I think the biggest problem right now is that Ember Data doesn't natively support pagination yet. There is no mention of the links key in Ember's JSONAPISerializer. Because of this, it means we'll either have to manually make an ajax request or, as @kellyselden said, transform the links into something we expect. I'm not a huge fan of either of these ideas.

Btw @kellyselden:

It is fuzzy for me what the response looks like because there are no examples.

Here's a link to clear it up: http://jsonapi.org/examples/#pagination

@kellyselden
Copy link
Collaborator Author

@dpatz Thanks for the example.

@IsaiahJTurner
Copy link

{
  "links": {
    "self": "http://example.com/articles",
    "next": "http://example.com/articles?page[offset]=2",
    "last": "http://example.com/articles?page[offset]=10"
  },
  "data": [{
    "type": "articles",
    "id": "1",
    "attributes": {
      "title": "JSON API paints my bikeshed!"
    },
    "relationships": {
      "author": {
        "links": {
          "self": "http://example.com/articles/1/relationships/author",
          "related": "http://example.com/articles/1/author"
        },
        "data": { "type": "people", "id": "9" }
      },
      "comments": {
        "links": {
          "self": "http://example.com/articles/1/relationships/comments",
          "related": "http://example.com/articles/1/comments"
        },
        "data": [
          { "type": "comments", "id": "5" },
          { "type": "comments", "id": "12" }
        ]
      }
    },
    "links": {
      "self": "http://example.com/articles/1"
    }
  }],
  "included": [{
    "type": "people",
    "id": "9",
    "attributes": {
      "first-name": "Dan",
      "last-name": "Gebhardt",
      "twitter": "dgeb"
    },
    "links": {
      "self": "http://example.com/people/9"
    }
  }, {
    "type": "comments",
    "id": "5",
    "attributes": {
      "body": "First!"
    },
    "relationships": {
      "author": {
        "data": { "type": "people", "id": "2" }
      }
    },
    "links": {
      "self": "http://example.com/comments/5"
    }
  }, {
    "type": "comments",
    "id": "12",
    "attributes": {
      "body": "I like XML better"
    },
    "relationships": {
      "author": {
        "data": { "type": "people", "id": "9" }
      }
    },
    "links": {
      "self": "http://example.com/comments/12"
    }
  }]
}

This is the homepage example of how links should work. Is there a way to implement this manually currently or does ember-infinity not support it at all?

@hhff
Copy link
Collaborator

hhff commented Dec 4, 2015

@IsaiahJTurner - I don't currently know a way to do it - but supporting JSONAPI is a priority.

I'm actually going to be tackling this in an app I'm working within a couple weeks. If you'd like to take a stab at it - please do! Failing that - it should be done before christmas 👍

@vsymguysung
Copy link

+1

@seanrucker
Copy link

@hhff did you end up working on this?

@hhff
Copy link
Collaborator

hhff commented Jan 14, 2016

@seanrucker no sir - it's on my list but keeps getting pushed off!

@ashikodes
Copy link

thanks @hhff it would be super cool if ember-infinity works with jsonapi pagination requirements. nice work tho

@ashikodes
Copy link

I see It can work with jsonapi pagination.

perPageParam: "page[size]",              // instead of "per_page"
  pageParam: "page[number]",                  // instead of "page"
  totalPagesParam: "meta.total",    // instead of "meta.total_pages"

total pages was calculated and sent with the metadata as

meta: {
     total: 5
}

And it works smooth

@kellyselden
Copy link
Collaborator Author

@andela-eashikodi That's awesome!

@hhff
Copy link
Collaborator

hhff commented Jan 26, 2016

sweet yeah - I've been seeing two different pagination styles in JSONAPI - one via like a next / previous URL in the payload - and the other via page[size] & page[number].

Great you got it working @andela-eashikodi !

@JakeDluhy
Copy link

I ran into a situation where I have an API that's using the links object, and had to hack together a solution...

Here's the way I did it:

// serializers/activity.js
// In normalizeResponse, grab the payload and set the params values to the index route
// Would probably prefer to do this in extractMeta, but that hook doesn't work for JSONAPISerializer atm
normalizeResponse(store, primaryModelClass, payload, id, requestType) {
  this._extractLinks(payload.links);

  return this._super(store, primaryModelClass, payload, id, requestType);
},
_extractLinks(linksObject) {
    let parser = document.createElement('a');
    let indexRoute = getOwner(this).lookup('route:index');
    ['prev', 'self', 'next', 'last'].forEach((key) => {
      if(linksObject[key]) {
        parser.href = linksObject[key];
        indexRoute.set(`${key}Params`, parser.search)
      } else {
        indexRoute.set(`${key}Params`, null);
      }
    });
  }

// routes/index.js
/**
 * Let's hack Ember Infinity =/
 */
_canLoadMore: Ember.computed('nextParams', function() {
  const nextParams  = this.get('nextParams');

  return (nextParams) ? true : false;
}),

_requestNextPage() {
  const nextParams = this.get('nextParams');
  const modelName   = this.get('_infinityModelName');

  return this.get('store')[this._storeFindMethod](modelName, nextParams || '')
  .then(this._afterInfinityModel(this));
}

// adapters/activity.js
urlForQuery(query) {
  return `${BASE_API_URL}/feeds/flat${query}`;
},

query: function(store, type, query) {
  var url = this.buildURL(type.modelName, null, null, 'query', query);

  return this.ajax(url, 'GET');
}

Honestly, it wasn't as messy as I thought, although setting the params values from the serializer wasn't great...

@andrewhavens
Copy link

I tried the approach that @andela-eashikodi mentioned, but it didn't work for me. The initial request happens correctly, so I receive the first page, but when I scroll to the bottom, I only see "Loading Infinite Model...", but there is never any AJAX request for the second page.

@utilityboy
Copy link

This is how I currently use Ember Data/EmberInfinity out of the box with JSONAPI resources using paged pagination:

import Ember from 'ember';
import InfinityRoute from 'ember-infinity/mixins/route';

export default Ember.Route.extend(InfinityRoute, {

  perPageParam: 'page[size]',
  pageParam: 'page[number]',
  totalPagesParam: 'meta.record-count',

  model() {
    let query = {
      sort: '-start-time',
      perPage: 20,
      startingPage: 1
    };
    return this.infinityModel('my-resource', query);
  }

});

@andrewhavens
Copy link

Thanks @utilityboy. As far as I can tell, my approach is identical.

My template:

{{#each model as |notification|}}
  {{my-component notification=notification}}
{{else}}
  <p>No Notifications</p>
{{/each}}
{{infinity-loader infinityModel=model}}

My route (in CoffeeScript):

`import Ember from 'ember'`
`import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin'`
`import InfinityRoute from "ember-infinity/mixins/route"`

FeedRoute = Ember.Route.extend AuthenticatedRouteMixin, InfinityRoute,
  pageParam       : "page[number]"
  perPageParam    : "page[size]"
  totalPagesParam : "meta.total"

  model: ->
    @infinityModel 'notification', perPage: 10, startingPage: 1

  actions:
    infinityModelUpdated: (totalPages) ->
      Ember.Logger.debug('updated with more items', totalPages)

    infinityModelLoaded: (lastPageLoaded, totalPages, infinityModel) ->
      Ember.Logger.info('no more items to load', lastPageLoaded, totalPages, infinityModel)

`export default FeedRoute`

The initial AJAX request is correct. The results are displayed and the payload includes meta: { total: 123 }. My actions aren't getting called either. What am I doing wrong?

@andrewhavens
Copy link

Ah, I think I figured out two things:

  1. infinityModelUpdated is not an action. It should be defined at the top level.
  2. I think the reason my "page 2" request is not happening is due to an issue with not calculating the offset correctly. It may be a CSS issue. Still trying to figure out to determine this.

@utilityboy
Copy link

@andrewhavens, can you show me resource?

@andrewhavens
Copy link

@utilityboy Sorry, I don't understand. What do you mean by resource?

@utilityboy
Copy link

@andrewhavens, your JSONAPI Resource.

@andrewhavens
Copy link

andrewhavens commented May 13, 2016

I got it working. Turns out my problem didn't have anything to do with JSON API, and everything to do with the loader not triggering the loadMoreAction when coming into the viewport. See my solution here: #82 (comment)

@allthesignals
Copy link

@utilityboy this works for me as well, however, Ember Infinity doesn't seem to know when to stop loading more resources (although I can confirm it is properly getting the totalPagesParam)

sporkexec added a commit to CashCacheTracker/cashcache-web that referenced this issue Nov 26, 2016
We're not scrolling the window (which is the default behavior), manually
specifying a scrollable just loads everything, and rolling our own with
ember-in-viewport doesn't work in Ember 2.9 for now. I'll just leave
this half-baked branch here. Maybe we can add a button to manually load
more until the dust settles? Maybe we should just fully roll our own?

And reminder: "total pages" in ember-infinity seems to actually mean
"total records".

adopted-ember-addons/ember-infinity#97
adopted-ember-addons/ember-infinity#82 (comment)
DockYard/ember-in-viewport#95
@superjova
Copy link

Has there been any activity on this issue? Is the best solution available to do it custom?

@hhff
Copy link
Collaborator

hhff commented Apr 4, 2017

@superjova - the best solution is to do it via a meta hash in the base of the response - aka the regular non-JSONAPI way.

I'd love to support this but I've not actually yet seen how to access the links portion of a JSONAPI payload from Ember Data. Does anyone know if it's supported yet?

@superjova
Copy link

If no JSON API links yet, how would you recommend this example with ember infinity?

  1. I have an API endpoint that returns all posts at /posts
  2. I have another API endpoint that returns logged user's posts at /user/posts

Both endpoints can be paginated and return the same post model.

It seems with ember infinity I can either infinite /posts or /user/posts from the 'post' model, but not both.

@hhff
Copy link
Collaborator

hhff commented Apr 10, 2017

Hi @superjova !

That's not really a problem fixed by JSONAPI - as even with JSONAPI the links property needs to "link" to another part of the same collection (meaning you still have two separately paginated collections).

I'd recommend having two separate infinity models. There's more complicated ways to do it, but in a lot of cases it's an over-optimization.

@superjova
Copy link

Creating separate infinity models is the road I've started heading down.

Conceptually, I was thinking a 'user' model would link to an articles relationship at '/api/users/1/articles'. After the first request, the articles end point would return pagination for ember infinity to plug into.

@hhff
Copy link
Collaborator

hhff commented Apr 10, 2017

Cool! Sounds good @superjova

@Duder-onomy
Copy link
Contributor

Got this working in our implementation.
We have a rails backend using jsonapi-resources and the 'Paged Paginator'.

The only gotcha in our implementation was that we did not expose the total pages count to the client, probably a similar issue to what @allthesignals was having. Ember Inifinity would only request one page, or keep requesting pages until I would kill the ember server. We had to explicitly allow the page count in the meta field like so: top_level_meta_include_page_count

The ember route looks like so:

import BaseRoute from 'shared-components/routes/base';
import InfinityRoute from 'ember-infinity/mixins/route';

export default BaseRoute.extend(InfinityRoute, {
  perPageParam: 'page[size]',
  pageParam: 'page[number]',
  totalPagesParam: 'meta.page-count',

  model() {
    return this.infinityModel('media-item', {
      perPage: 20,
      startingPage: 1,
    });
  },
});

works great! good job Ember Infinity team. Since jsonapi seems to the the preferred format for ember and the default for ember-data, I think something alluding to this should be in the readme. My opinion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests