I can get data into my app with ic.ajax, but it seems like I should be using the RESTAdapter. The explanations are so simplified, that I'm not sure what to do in various cases. This is what I think should work: (and does with fixtures, a local express server, and http-mocks)
I'm going to use tumblr as the example - since it's always been friendly API in general.
import Ember from 'ember';
import config from './config/environment';
var Router = Ember.Router.extend({
location: config.locationType
});
Router.map(function() {
// tumblr posts
this.resource('posts', {
path: '/tumblr'
});
});
export default Router;
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return this.store.find('post');
}
});
So, as far as I can tell - find()
is some magic ajax call... but what if I want to specify jsonp or something?
import DS from 'ember-data';
var tumblrBlogName = 'feministlibraryonwheels'; // friends site
var tumblrApiKey = 'UbB4p0GxqNa6wUa8VwpIdqtywjIiA6vljZXyI9wkx9hnQnAFyk';
var tumblrRequestUrl = 'http://api.tumblr.com/v2/blog/' + tumblrBlogName + '.tumblr.com' + '/posts?api_key=' + tumblrApiKey;
export default DS.RESTAdapter.extend({
host: tumblrRequestUrl
});
This is a little whacky, because the long tumblr endpoint-thing is so squirly - I feel like it should just be http://api.tumblr.com
and maybe there is another way to specify the other stuff... or does it just somehow know... very confused... seems like namespace: 'v2' is what that option would be for...
<section class='container stage'>
<div class='inner-w'>
<h2>tumblr posts</h2>
<ul class='block-list event-list'>
{{#each}}
<li>
{{title}}
</li>
{{else}}
No posts are coming up... what's up with that?
{{/each}}
</ul>
</div>
</section>
Then this {{#each}}
just knows what it's supposed to look for in most cases - but I would like to be explicit.
In all the tutorials I've seen, it's a local server - or http-mocks - and it's just something like this:
import DS from 'ember-data';
export default DS.RESTAdapter.extend({
host: 'localhoststuff:3000',
namespace: 'api'
});
Then on top of this - I get what seems like a cors issue GET http://api.tumblr.com/v2/blog/feministlibraryonwheels.tumblr.com/posts?api_key=UbB4p0GxqNa6wUa8VwpIdqtywjIiA6vljZXyI9wkx9hnQnAFyk/posts 401 (Not Authorized)
and it's not like it's really hidden... [http://api.tumblr.com/v2/blog/feministlibraryonwheels.tumblr.com/posts?api_key=UbB4p0GxqNa6wUa8VwpIdqtywjIiA6vljZXyI9wkx9hnQnAFyk][1]
Also, here is some ic.ajax()
attempts I was making as well. The payload gets to the console - but it gets foggy when trying to get the data into the templates
import Ember from 'ember';
import ajax from 'ic-ajax';
export default Ember.Route.extend({
model: function() {
var libraryData = ajax({
url: 'http://www.librarything.com/api_getdata.php?userid=F.L.O.W.&showstructure=1&showTags=1&booksort=title_REV',
type: 'get',
dataType: 'jsonp'
});
console.log(libraryData);
return libraryData;
}
});
EDIT: the 2.4 docs are pretty great. Ember data is stable. You need to know what type of JSON you're getting an if it isn't JSON API format then you need to serialize the data and mold it into that format.
Using Ember 1.13.3.
With regard to Tumblr specifically, I have the following in adapters/post.js. Hopefully this helps you.
import DS from "ember-data";
export default DS.RESTAdapter.extend({
host: 'https://api.tumblr.com',
namespace: 'v2/blog/example.tumblr.com',
apiKey: 'example',
ajaxOptions() {
var hash = this._super.apply(this, arguments);
hash.data = hash.data || {};
hash.data.api_key = this.get('apiKey');
hash.dataType = 'jsonp';
return hash;
}
});
Tools (Adapters and Serializers)
To connect to an API that isn't Ember-Data friendly (like tumblr) you have to customize an adapter such as the RESTAdapter or JSONAPIAdapter to build the request properly.
(The JSONAPIAdapter is a subclass of RESTAdapter that adjusts a few things for a JSON API such as the Accept: application/vnd.api+json
header required by the specs)
Further, if the data retrieved doesn't follow the Ember Data JSON JSON API format, you should customize a serializer such as the JSONSerializer, RESTSerializer or JSONAPISerializer to format and massage the data by selecting the most appropriate serializer to the data returned by your backend. Here's a short summary of these 3 serializers:
JSONSerializer:
Expects the usual JSON format
{ "id": 1, "name": "Bob", "friends": [array of friend ids], "links": { "home": "/bobshome" } }
RESTSerializer:
Expects a similar JSON format (with a "root"):
{ "user": { "id": 1, "name": "Bob", "friends": [array of friend ids], "links": { "home": "/bobshome" } } }
JSONAPISerializer
In general, we can mix and match adapters and serializers to whatever fits best our data and URL endpoint structure. Each of these can be applied on a per-application or per-model basis.
Methods
When building the request, depending on the exact behavior you need, you should override appropriate hooks provided by the Ember Data Store (in the Adapter):
Each of the hooks above has an appropriate normalize{{HOOK_NAME}}Response
method in the serializer (for example normalizeFindRecordResponse
) where data can be massaged depending on the way we request it (which hook we call):
Examples
Let's say we want to get Bob's Tumblr blog called "mynameisbob".
Here's a generic example how to do that:
export default DS.RESTAdapter.extend({
namespace: 'v2/blog/', // https://guides.emberjs.com/v2.0.0/models/customizing-adapters/#toc_endpoint-path-customization
host: 'https://api.tumblr.com', // https://guides.emberjs.com/v2.0.0/models/customizing-adapters/#toc_host-customization
headers: { // https://guides.emberjs.com/v2.0.0/models/customizing-adapters/#toc_headers-customization
'api_key': 'abcdefg'
}
// set any default ajax options you might need
ajaxOptions(url, type, options = {}) {
options.crossDomain = true; // make it CORS
options.dataType = 'jsonp';
return this._super(url, type, options);
}
// find a blog
findRecord: function(store, type, id, snapshot) {
const URL = this.buildURL(type.modelName, id, snapshot, 'findRecord');
return this.ajax(URL, 'GET');
}
});
Usage in a Route
:
// ...
model() {
return this.store.findRecord('blog', 'mynameisbob');
}
// ...
However, there are multiple ways to achieve this. You can also store the api key and the host url as properties of your adapter and just use them to build the URL (using the buildURL hook):
export default DS.RESTAdapter.extend({
hostUrl: 'https://api.tumblr.com/v2/blog'
apiKey: 'abcdefg',
buildURL: function(modelName, id, snapshot, requestType, query) {
// customize the url based on the parameters
// lets assume the id is the blog name
return `${this.get('hostUrl')}/${id}.tumblr.com/posts?api_key=${this.get('apiKey')}`;
}
// find a blog
findRecord: function(store, type, id, snapshot) {
const URL = this.buildURL(type.modelName, id, snapshot, 'findRecord');
return this.ajax(URL, 'GET');
}
});
Here's a Github Repo for a simple Ember app communicating with the Github API
Community driven Adapters to serve as examples:
Some useful readings:
Here is a simple example on how to write a custom RestAdapter. Essentially, what you need to do is to overwrite the store lookup hooks you need (find
, findAll
, findQuery
...), as well as buildURL()
.
Since it's an external API and we need to worry about CORS, we should also override the ajax
hook.
Custom Tumblr Adapter:
App.TumblrAdapter = DS.RESTAdapter.extend({
buildURL: function(type, id, record) {
var tumblrBlogName = 'feministlibraryonwheels';
var tumblrApiKey = 'abcdefg';
var tumblrRequestUrl = 'http://api.tumblr.com/v2/blog/' + tumblrBlogName + '.tumblr.com' + '/posts?api_key=' + tumblrApiKey;
return tumblrRequestUrl;
},
ajax: function(url, method, hash) {
hash = hash || {}; // hash may be undefined
hash.crossDomain = true; // make it CORS
hash.xhrFields = {withCredentials: true};
return this._super(url, method, hash);
},
find: function(store, type, id, record) {
// customization here or within buildURL
return this.ajax(this.buildURL(), 'GET');
},
findAll: function(store, type, sinceToken) {
// customization here or within buildURL
return this.ajax(this.buildURL(), 'GET');
},
findQuery: function(store, type, query) {
// customization here or within buildURL
return this.ajax(this.buildURL(), 'GET');
},
findMany: function(store, type, ids, owner) {
// customization here or within buildURL
return this.ajax(this.buildURL(), 'GET');
}
});
If the response isn't properly formatted for Ember Data, we can fix it quickly in the ajax
hook with a simple promise:
ajax: function(url, method, hash) {
hash = hash || {}; // hash may be undefined
hash.crossDomain = true; // make it CORS
hash.xhrFields = {withCredentials: true};
return this._super(url, method, hash).then(function(json) {
// Massage data to look like RESTAdapter expects.
return { tumblrs: [json] };
});
},
If you wish to properly define all the Models with relationship, you will need to implement a custom RESTSerializer to massage the data properly.
Here is a simple jsbin example I came across on while researching these things myself.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With