I'm trying to get data from an API like this:
App.Store = DS.Store.extend({
revision: 12,
adapter: DS.RESTAdapter.create({
host: 'http://api.my-api/v1/products(name=my-name)'
})
});
App.Product = DS.Model.extend({
name: DS.attr('string')
});
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return App.Product.findQuery({show: 'sku,name', format: 'json', apiKey: 'MyApIkEy123'});
}
});
The error I get in the console is:
Error while processing route: index undefined is not a function (evaluating 'App.Product.findQuery({show: 'sku,name', format: 'json', apiKey: 'MyApIkEy123'})')
The JSON should look like this:
{
"from": 1,
"to": 10,
"total": 10,
"products": [
{
"sku": 1234567,
"name": "Great Product"
}
}
They are several problems on your post.
The first one is that you do not run App.Product.findQuery in your route but a this.store.find(yoursamequery) as App.Product extends DS.Model and DS.Model dosen't have findQuery method (thus you get undefined is not a function :))
http://emberjs.com/api/data/classes/DS.Model.html
I think that your "format" and "apiKey" are not data filter but request parameters which have to be passed to your backend api right ? If so you should create an applicationAdapter with those parameters defined as in the documentation example :
http://emberjs.com/api/data/classes/DS.RESTAdapter.html
In the model hook, try using:
return this.store.findQuery('product', {show: 'sku,name', format: 'json', apiKey: 'MyApIkEy123'});
It looks like you are trying to get your API to provide attributes that aren't in your model (i.e. sku, salePrice). Is that right? What does the response to that API call look like? If Ember Data is trying to set those attributes in your model object and not finding them, this could be the issue.
Related
I have been looking at EmberJS tutorials, but all of them use FixtureAdapter, and I'm trying to switch to RESTAdapter, but I'm facing a persistent error
Error: Assertion Failed: Expected an object as `data` in a call to push for Books.Book , but was undefined
here's the code for the adapter:
Books.ApplicationAdapter = DS.RESTAdapter.extend({
host: 'http://localhost:24818/api/'});
and calling the api in the router:
Books.BooksRoute = Ember.Route.extend({
model: function() {
return this.store.find('book',1);
}
});
How do I get store.find to return a JSON to be used in the Handlebars template?
Many thanks,
Edit: this is my API response:
[{"Id":1,"Title":"Pride and Prejudice","Year":1813,"Price":9.99,"Genre":"Comedy of manners","AuthorId":1,"Author":null},{"Id":2,"Title":"Northanger Abbey","Year":1817,"Price":12.95,"Genre":"Gothic parody","AuthorId":1,"Author":null},{"Id":3,"Title":"David Copperfield","Year":1850,"Price":15.00,"Genre":"Bildungsroman","AuthorId":2,"Author":null},{"Id":4,"Title":"Don Quixote","Year":1617,"Price":8.95,"Genre":"Picaresque","AuthorId":3,"Author":null}]
Edit: add model definition:
Books.Book = DS.Model.extend({
title: DS.attr('string'),
year: DS.attr('number'),
price: DS.attr('number'),
genre: DS.attr('string'),
authorId: DS.attr('number'),
author: DS.attr('string')
});
I think you need to normalize your response. i.e
"books": [
{
"id":1,
"Title":"Pride and Prejudice",
"Year":1813,
"Price":9.99,
"Genre":"Comedy of manners",
"AuthorId":1,"Author":null
},
{
"id":2,
"Title":"Northanger Abbey",
"Year":1817,
"Price":12.95,
"Genre":"Gothic parody",
"AuthorId":1,
"Author":null
},
{
"id":3,
"Title":"David Copperfield",
"Year":1850,
"Price":15.00,
"Genre":"Bildungsroman",
"AuthorId":2,
"Author":null
},
]
If you have no control over the endpoint. You will need to setup a serializer or normalizer to normalize your response into this expected format.
--
Useful Links
Ember-data Model Maker
Ember JSON Conventions
REST Serializer
I have two models:
App.Offer = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
products: DS.hasMany('product')
});
App.Product = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
offer: DS.belongsTo('offer')
});
And the server is answering with record and array of ids in this way (for example if the rest adapter asks for /offers/1):
{ "offer": [ { "id": 1, "name": "aaaaaaaaa", "description": "aaaa", "product_ids": [ 1, 2 ] } ] }
but now how can I get the products? I have a route like this:
App.OffersRoute = Ember.Route.extend({
model: function() {
var offer = this.get('store').find('offer', 1);
return offer
}
});
In Ember guide is written that if you want the products you should do:
offer.get('products');
Ok, but where should I put this? in the model hook? in a Controller property?
I've tried many things but I can see no network request to products?id[]=1&id[]=2 as I expected (the server is responding correctly to this request);
Can someone please give an example showing how I can find an offer, its products and use this data in my template?
If you're using the RESTAdapter your data needs to be in this format (if you don't want to return it in this format you can create a custom serializer and fix up the json).
2 differences:
the item under offer shouldn't be an array, since you were looking for a single item it should be an object
the key product_ids should be products, product_ids is the format that the ActiveModelAdapter/ActiveModelSerializer use.
JSON
{
"offer":
{
"id":1,
"name":"aaaaaaaaa",
"description":"aaaa",
"products":[
1,
2
]
}
}
The hasMany relationship should be marked as async if you're expecting it to be returned in a separate payload.
App.Offer = DS.Model.extend({
name: DS.attr('string'),
description: DS.attr('string'),
products: DS.hasMany('product', {async:true})
});
I hooked it up in jsbin below, but I didn't hook up a result from products?ids[]=1&ids[]=2 (note ids[]=, not id[]=), if you check the network tab you'll see the request being issued (but it'll crash since there is no result).
http://emberjs.jsbin.com/OxIDiVU/345/edit
In my Emberjs application I have an Employee model which I should load through a REST Get API call, where I have to authenticate the API first for a token then start loading the data, I know how to do this easily using JQuery but not sure how I can implement this in EmberJS, so I will appreciate it so much if anyone can instruct me how to do so.
Below is the JQuery code I use for authentication, extracting the employees data, as well as my EmberJS model code
Thanks
Authentication:
$.ajax
({
type: "POST",
url: "http://portal.domainname.com/auth",
dataType: 'json',
async: false,
data: JSON.stringify({
Login: "logmein#email.com",
Password : "test"
}),
success: function(data) {
console.log(data); //Here I get the token needed for further calls...
},
error: function(xhr, error){
console.debug(xhr); console.debug(error);
}
});
Calls to load employees data:
$.ajax ({
type: "GET",
url: "http://portal.domainname.com/employees",
dataType: 'json',
async: false,
beforeSend: function (xhr) {
xhr.setRequestHeader ("Token", "0000000-0000-0000-0000-00000000");
},
success: function(data) {
console.log(data);
},
error: function(xhr, error){
console.debug(xhr); console.debug(error);
} });
EmberJS Model
App.Store = DS.Store.extend({
revision: 11
});
App.Employee = DS.Model.extend({
employeeID: DS.attr('string'),
employeeName: DS.attr('string')
});
App.Store.reopen({
adapter: 'DS.RESTAdapter'
});
You can add headers to all Ember AJAX requests like this:
App.Store = DS.Store.extend({
revision: 13,
adapter: DS.RESTAdapter.extend({
ajax: function(url, type, hash) {
if (!hash) {
hash = {};
}
hash.beforeSend = function(xhr) {
xhr.setRequestHeader("Authorization", "Token " + window.sessionToken);
};
return this._super(url, type, hash);
}
})
});
I use this code in production.
A very real & viable solution is to avoid using EmberData and just use ajax the way you already know. Take a look at this tutorial from a founder of Discourse (which uses Ember without Ember Data):
http://eviltrout.com/2013/03/23/ember-without-data.html
As a hack you could use something like https://api.jquery.com/jQuery.ajaxPrefilter/ for adding the header with the token to every call. However, I think you should use a dedicated auth library for this.
Also your store has revision: 11 - that is for an old version I believe.
Try something like this:
App.ApplicationAdapter = DS.RESTAdapter.extend({
setHeaders: function() {
this.set('headers', { "Token": "0000000-0000-0000-0000-00000000" });
}.on('init');
});
I think you'll need ember-data-1.0.0-beta.x for this to work.
I am using the Ember-Data Rest-Adapter and the JSON returned from my server looks basically like the one in the Active Model Serializers Documentation
{
"meta": { "total": 10 },
"posts": [
{ "title": "Post 1", "body": "Hello!" },
{ "title": "Post 2", "body": "Goodbye!" }
]
}
Fetching the data from the server works but unfortunately I am not able to figure out where I can access the meta information from my JSON response.
Based on my research in ember-data's github issue, support for meta information seems to be implemented with commit 1787bff.
But even with the test cases I was not able to figure out how to access the meta information.
App.PostController = Ember.ArrayController.extend({
....
requestSearchData: function(searchParams){
posts = App.Post.find(searchParams);
this.set('content', posts);
// don't know how to access meta["total"]
// but I want to do something like this:
// this.set('totalCount', meta["total"])
}
})
Can anybody of you shed some light on this for me, please? I am aware that the Ember api is moving fast but I am sure I am just missing a small part and that this is actually possible.
I found a cleaner approach for extracting meta information from the server response with ember-data.
We have to tell the serializer which meta-information to expect (in this case pagination):
App.serializer = DS.RESTSerializer.create();
App.serializer.configure({ pagination: 'pagination' });
App.CustomAdapter = DS.RESTAdapter.extend({
serializer: App.serializer
});
App.Store = DS.Store.extend({
adapter: 'App.CustomAdapter'
});
After that every time the server sends a meta-property with a pagination object this object will be added to the store's TypeMaps property for the requested Model-Class.
For example with the following response:
{
'meta': {'pagination': { 'page': 1, 'total': 10 } },
'posts':[
...
]
}
The TypeMap for the App.Post-Model would include the pagination object after the posts have loaded.
You can't observe the TypeMaps-property of the store directly so I added an computed property to the PostsController to have access to the requests pagination meta information:
App.PostsController = Ember.ArrayController.extend({
pagination: function () {
if (this.get('model.isLoaded')) {
modelType = this.get('model.type');
this.get('store').typeMapFor(modelType).metadata.pagination
}
}.property('model.isLoaded')
});
I really don't think that's a great solution to the meta information problem but this is the best solution I was able to come up with yet with Ember-Data. Maybe this will be easier in the future.
I figured out a way to access the meta information passed in a response. But unfortunately it really does not seem to be supported by ember-data out of the box and I am writing the meta information to a global variable that I am then accessing via bindings in the requesting controller.
I ended up customizing the serializer the RESTAdapter is using:
App.CustomRESTSerializer = DS.RESTSerializer.extend({
extractMeta: function(loader, type, json) {
var meta;
meta = json[this.configOption(type, 'meta')];
if (!meta) { return; }
Ember.set('App.metaDataForLastRequest', meta);
this._super(loader, type, json);
}
});
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.RESTAdapter.create({
bulkCommit: false,
serializer: App.CustomRESTSerializer
})
});
I am aware that this is not particularly pretty and actually think that this is against what ember-data expects us to do but fot the time being it's working correctly.
I will try to get this working with ember-data in a better way and submit a pull request when it is working or open an issue on github when anybody else is interested in getting this to work.
If anybody finds a more clean solution to this please let me know.
I think the immediate fix for you would be to attach totalCount to your model(recordArray), see this thread.
Another way to go would be to create your own adapter:
DS.Adapter.create({
find: function (store, type, id) {
$.ajax({
url: type.url,
dataType: 'jsonp',
context: store,
success: function(response){
this.load(type, id, response.data);
}
});
},
findAll: function(store, type) {
$.ajax({
url: type.url,
dataType: 'jsonp',
context: store,
success: function(response){
this.loadMany(type, response.data);
}
});
}
});
Response parameter in success callback in findAll method, should be an object that you need:
response: {
meta: {
totalCount: 10
},
posts: [{}, {}]
}
Hope this helps.
I'd like to display a list populated with data from a JSON file. I would use a modified Adapter. Here's the code so far, including path to the test json file (since I'd hope to get some help without the use of FIXTURES)
I'm unsure on how to pushObject for results using Ember-Data. I know I'm probably still not getting some concepts for Ember-Data.
My question is: How do I get a list from the JSON file based on the following code.
JavaScript
App = Em.Application.create();
App.store = DS.Store.create({
revision: 4,
adapter: App.adapter
});
App.adapter = DS.Adapter.create({
findAll: function (store, type) {
console.log('finding');
var url = type.url;
$.getJSON(url, function(data) {
store.loadMany(type, data);
});
}
});
App.Person = DS.Model.extend({
fName: DS.attr('string'),
surname: DS.attr('string'),
url: 'test.json',
});
App.personController = Ember.ArrayController.create({
content: [],
init: function(){
this._super();// create an instance of the person model
this.set('person', App.store.findAll(App.Person));
},
});
HTML
<script type="text/x-handlebars">
{{#each App.personController}}
<p>{{fName}} {{surname}}</p>
{{/each}}
</script>
EDIT
I´ve updated the code in the jsfiddle on #pauldechov recommendations, still no luck.
Here's the content for test.json, so the whole app can be recreated:
[{"name": "Bruce","surname": "Banner"},
{"name": "Peter","surname": "Parker"},
{"name": "Natasha","surname": "Romanoff"},
{"name": "Steve","surname": "Rogers"},
{"name": "Tony","surname": "Stark"}]
The url property should be defined as such:
App.Person = DS.Model.extend({
fName: DS.attr('string'),
surname: DS.attr('string'),
}).reopenClass({ url: 'test.json' });
...as an instance variable rather than as a class variable (see example: https://github.com/emberjs/data#find). The way you defined it (or if you use .reopen({...}) instead of .reopenClass({...}), it would be accessible via (a specific) person.url rather than type.url.
AFAIK you can't add a .json file to jsfiddle's resources; it won't be accessible. Querying the full url of test.json yielded a cross domain issue.
You may need to place the model(s) above App.adapter, above App.store. Try this if it's still failing.
Peripheral observation: "name" in test.json vs. "fName" in the code?
App.personController = Ember.ArrayController.create({
content: [],
init: function(){
this._super();// create an instance of the person model
this.set('person', App.store.findAll(App.Person));
},
});
Why set person? Should it not be set content?