In ember data, if you want to fetch the collection of a model, it's convention to use this:
this.store.findAll('order');
or with a filter, this:
this.store.find('order', {shopId: 63});
So you pass the model name, and Ember-data will build the URL for you, which would look something like (depending on your adapter):
GET /api/orders
GET /api/orders?shopId=63
So this does two things
Build the URL to fetch data from the api
Map the collection as JavaScript objects, using the model that you passed as 1st argument
But what if I want to fetch orders from two URLs; /api/orders and /api/new_orders ?
The first one will work as usual: this.store.findAll('order'), but is there a way to override the api path that you fetch from?
Maybe something like this.store.find('order', {path: '/new_orders'})?
So that I can end up with a collection of objects modelled with my order model, but fetched from a different route
You need to have a rest adapter for this store and override the findAll method. The default implementation is as such
findAll: function(store, type, sinceToken) {
var query, url;
if (sinceToken) {
query = { since: sinceToken };
}
url = this.buildURL(type.modelName, null, null, 'findAll');
return this.ajax(url, 'GET', { data: query });
}
buildUrl will return a proper endpoint for your first url. you could then parse this url and modify it to make a second request with the same data, but with to your second endpoint. Than you could merge the responses or use them separately.
Reference: https://github.com/emberjs/data/blob/v1.13.5/packages/ember-data/lib/adapters/rest-adapter.js#L398
Related
I'm working with an API that only accepts unencoded query parameters. I notice that when I make a query in Ember like so:
this.store.query('listing', {filter: params});
The uri is encoded when it hits the api:
/v1/listing?filter%5Bcategory%5D=123
What I need is for the query parameters to get to my API unencoded, eg:
/v1/listing?filter[category]=123
Can anybody give me a steer on what is the right way to do this in Ember?
So the solution I ended up implementing is not great but it does the trick until I find a better way.
Ember uses Ajax to form the request, so what I did was override 'query' in my JSONAPIAdapter to pass through a custom set of options. My query now looks like this:
query(store, type, query) {
let url = this.buildURL(type.modelName, null, null, 'query', query);
if (this.sortQueryParams) {
query = this.sortQueryParams(query);
}
query = decodeURI(Ember.$.param(query));
let options = {
"processData": false,
"data": query
};
return this.ajax(url, 'GET', options);
},
The key was to stop ajax's automatic processing of the data object, and decode the result of jquery's 'param()' helper function, which is used to convert the query param object into a serialized string.
The result is a decoded query param string like so:
/v1/listing?filter[category]=123
HI my basic model which fetches data from server is working perfect. I want to implement a search feature. When user enters any data the request goes to browser and desired model is returned.
var Book = Backbone.Model.extend({
urlRoot: '/books'
});
render: function(options) {
books = new Book({id:options.name});
books.fetch();
}
where
name = "search/"+dynamic_data;
Request URL that is being formed when i pass --> 'life' in variable dynamic_data
http://host/path/search%2Flife
Request URL that i want
http://host/path/search/life
How can I encode/escape my string to achieve the desired result. I have tried escape(), encodeURI(), encodeURIComponents
A workaround to solve this is create one more model with urlRoot as /books/search and pass just name . I don't think this is correct. Should I use this ?
According to your additionnal precisions stating that life is actually a book name...
It looks like Backbone is better integrated with RESTful API's. In REST, your urls should not contain verbs and to search books, you would do a GET /books/?name=life.
In that case, you would only have to define a Backbone.Collection like:
var BooksCollection = Backbone.Collection.extend({
model: Book,
url: '/books'
});
The to fetch books:
var books = new BooksCollection();
books.fetch({data : {name: 'life'}}); //GET /books/?name=life
If you really want your search operation to target /search/:name, you will have to check the Backbone.Collection api, but I think you will want to look at http://backbonejs.org/#Sync
You could override your collection's sync method to something like:
Backbone.Collection.extend({
...
sync: function (method, model, options) {
//for read operations, call the search url
if (method === 'read') {
options.url = '/search/' + options.data.name;
delete options.data.name;
}
//call the default sync implementation
return Backbone.Collection.prototype.sync.apply(this, arguments);
}
});
In this cased calling books.fetch({data : {name: 'life'}}); will result in GET /books/life.
Here's a fiddle that shows the example.
this would work:
books = new Book({id:options.name}, {url: options.name));
decodeURIComponent() will decode http://host/path/search%2Flife to http://host/path/search/life.
I have a REST API serving a few URLs:
/rest/messages
provides all messages. A message is a JSON/Backbone Model
{
title: 'foo',
body : 'bar'
}
To get a single message I have:
/rest/messages/:id
Is it possible to fetch a Backbone Collection using message IDs array? I don't want the whole message list, but just a few messages I specify by ID.
I could fetch Models one-by-one and fill up the Collection, but I'm wondering if Backbone has a cleaner way to do this.
Thanks
According to documentation, you can pass ajax options to the fetch call. So, you can pass ids as data attribute to the fetch call being done and based on it, return the respective models from the server.
For example (when doing fetch),
collection.fetch({
data : {
message_ids : [1, 3, 5] // array of the message ids you want to retrieve as models
}
})
This message_id array will be accessible as parameters (not sure of the name in your case) in the server code being executed at /rest/messages, from there you can return only specific models based on ids you receive as message_ids. The only thing you need is, client side must be aware of the ids of all the message models it needs.
You can use any data structure instead of array to send message_ids.
The url property of collection reference to the collection location on the server. When you use fetch, backbone uses that url.
The url property can be also a function that returns the url. So you can do something like that:
var ids = [1,2,3]
var messages = new MessegecCollection();
messages.url = function() {
return "/rest/messages/"+ids.join("-"); //results "/rest/messages/1-2-3"
}
messages.fetch();
You can also create a method in your collection that generated and set the url, or even fetchs a set of models.
Now all you have to do is to support this url: /rest/messages/1-2-3
Hope this helps!
I have exposed to Backbone an API that returns user profiles with this structure:
{id: 1, following: {...}}. I only want to use the dictionary inside of the "following" attribute. How would I do that? Right now, I have a model with a URL to the API. I have a collection that uses this model. I do a fetch() on the collection, but I only want to use the dictionary inside of "following".
You should use parse to extract what you want from the the response:
parse model.parse(response)
parse is called whenever a model's data is returned by the server, in fetch, and save. The function is passed the raw response object, and should return the attributes hash to be set on the model.
So you'd want something like this in your model:
parse: function(response) {
return response.following;
}
I have a collection which holds some of the users. Some information that is needed is how many total there are, how many pages, etc. How do I pass these back to the client? Or do they have to come from a separate view in which case I will need more than one ajax call? I'd like to have the collection fetch() and also receive some of this "meta data". What's a good way for handling this?
Generally, you need to handle this in the collection class' parse method. Its responsibility is to take the response and return back an array of model attributes. However, you could do more than that if you wished if you didn't mind the parse method having this additional responsibility.
UserList = Backbone.Collection.extend({
model: User,
url: '/users',
parse: function(data) {
if (!data) {
this.registered_users = 0;
return [];
}
this.registered_users = data.registered_users;
var users = _(data.users).map(
function(user_data) {
var user = {};
user['name'] = user_data.name;
return user;
}
);
return users;
}
});
So in the trivial example above, presume the server returns a response which contains a count of registered users and and an array of user attributes. You would both parse through and return the user attributes and you would pick off the registered user count and just set it as a variable on the model.
The parse method would get called as part of a fetch. So no need to modify the fetch, just use the built-in hook method that you have.
Purists would say that you are giving the parse method a secondary responsibility which might surprise some people (e.g. returning something and modifying model state). However, I think this is okay.
One way to do this is to override the Collection::fetch() method so that it parses this metadata out of the response. You could have your backend return a response like this:
{
"collection": [
{ ... model 1 ... },
{ ... model 2 ... },
...
],
"total_rows": 98765,
"pages": 43
}
In your fetch method which overrides the original Backbone.Collection::fetch() method, you can handle each property of the object separately. Here's you could do the override with a slightly modified fetch method:
_.extend(Backbone.Collection.prototype, {
fetch : function(options) {
options || (options = {});
var collection = this;
var success = options.success;
options.success = function(resp) {
// Capture metadata
if (resp.total_rows) collection.total_rows = resp.total_rows;
if (resp.pages) collection.pages = resp.pages;
// Capture actual model data
collection[options.add ? 'add' : 'refresh'](
collection.parse(resp.collection), options);
// Call success callback if necessary
if (success) success(collection, resp);
};
options.error = wrapError(options.error, collection, options);
(this.sync || Backbone.sync).call(this, 'read', this, options);
return this;
});
Note that this approach using _.extend will affect all your classes which extend Backbone.Collection.
This way, you don't have to make 2 separate calls to the backend.
I would bootstrap the information at pagecreation. Write the information into the html document when the server creates the site. Like that you don't have to have an ajax call at all. I do that with the whole collection in ordner not to first load the page and then wait for the ajax call to return the information needed.
Code example with Python:
Line 64: https://github.com/ichbinadrian/maps/blob/master/python/main.py <- from here
Line 43: https://github.com/ichbinadrian/maps/blob/master/templates/index.html <- into here