Let's say that I have an Ember.Data query I'd like to make:
this.store.find('items', {itemIds: [1,2,3]});
By default, Ember.Data creates a URL that looks like this:
items?itemIds%5B%5D=1&itemIds%5B%5D=2&itemIds%5B%5D=3
But the REST api I am connecting to wants it in this format:
items?itemIds=1&itemIds=2&itemIds=3
How do I achieve this adaptation?
Extend the RESTAdapter and override the ajax method and create the URL that you want to use based on the circumstances.
App.ItemsAdapter = DS.RESTAdapter.extend({
ajax: function(url, type, options){
if(myCircumstance){
var data = options.data;
delete options.data;
url = url + ......;
}
return this._super(url, type, options);
}
});
REST Adapter implementation: https://github.com/emberjs/data/blob/v1.0.0-beta.16.1/packages/ember-data/lib/adapters/rest-adapter.js
From what I see looking on the ember data code, you'd have to overwrite the RestAdapter's findQuery or ajax method, see http://emberjs.com/api/data/classes/DS.RESTAdapter.html#method_findMany (see https://github.com/emberjs/data/blob/v1.0.0-beta.16.1/packages/ember-data/lib/adapters/rest-adapter.js). Both are private, but the store expects the findQuery to be there (https://github.com/emberjs/data/blob/v1.0.0-beta.16.1/packages/ember-data/lib/system/store/finders.js#L137), so I wouldn't expect this behaviour to change soon.
If you use this for production, you'd better open a bug report to have this or something similar exposed as a public hook, as I cannot see one being there thus far.
#Kingpin2k's answer is the right direction, but the solution is even simpler. To create the query params, Ember Data simply yields the data object, wrapped in the options object, to the jQuery.ajax function.
Knowing that, we just need another query param serializer. By default, jQuery will serialize arrays the way TS described. You can change the way of serialization by overriding the $.param method. But luckily we don't even have too, because the $.param function has a second argument called traditional serialization. If set to true, the serialization will be like TS wanted*.
The jQuery.Ajax function also has the traditional flag to use the traditional style of param serialization. Combining this facts, you just need to set this flag yourself:
DS.RESTAdapter.extend({
ajax(url, type, options) {
if (options) {
options.traditional = true;
}
return this._super(...arguments);
}
});
P.S. If you use the JSONAPIAdapter, the trick is the same, because the JSONAPIAdapter extends the RESTAdapter.
*If you need another serialization, you need to override the $.param.
Related
I am using Ember 2.16.0 and I can import string into Handlebar but when I try to access JSON property I am not getting expected result. Is there a helper that will convert a string into JSON inside Handlebar template?
Similar to the answer the OP posted, except I'd make the schemaJson property a computed property so that if schemasString changed, it would automatically update:
export default Component.extend({
schemasJson: computed('schemasString', function() {
return JSON.parse(this.schemasString);
}
});
Generally the intent of Handlebars is to keep as much logic out of the template as possible, so more typically in Ember you might perform the JSON parsing in a class, like a controller or component. That way by the time the data is sent to the template it’s already in the final data format you need. Is that an option for you?
I ended up using something like this to make data available to the component template. Not 100% sure if this is the best approach.
export default Component.extend({
init() {
this._super(...arguments);
this.set('schemasJson', Ember.$.parseJSON(this.schemasString));
}
});
--Using Ember Data 2.7.1--
I am trying to reverse the order of a collection of records without first turning them into an array using toArray(). This collection of objects comes from the promise returned by this.store.findAll('history-item').
I want to do this the ember way instead of making them plain javascript. I am getting a TypeError: internalModel.getRecord coming from record-array.js. For some reason when it is trying to do objectAtContent(), the content it is looking seems to not have a type. Through the stack trace I can see that the object I am dealing with is [Class], class being the history-item model. A few stack calls before the objectAtContent(), the object being dealt with switches from that history-item model to some other Class object that has no type attribute.
I am able to use Ember Inspector to see my data correctly, and if I just displayed the original collection of records on my template, it shows properly.
Has anyone run into this?
Some thoughts and considerations:
-Is there anything special about how findAll() works with its promise that doesn't allow for reversal since it is reloading in the background? I do want it to keep reloading live data.
-I am using ember-cli-mirage to mock my db and endpoints and I've follow the instructions to the letter I think. I am using an unconfigured JSONAPISerializer for mirage and and a unconfigured JSONAPIAdapter for ember. Could it have anything to do with metadata that is being sent from the back? Could it have something to with the models or records not being set up? Is there something special I have to do?
Route Segment that defines model and tries to reverse it:
[note: I know it may not be convention to prep the data (ordering) in the route but I just put it in here for ease of description. I usually do it outside in the controller or component]
model(){
return this.get('store').findAll('history-item').then(function(items){
return items.reverseObjects();
}).catch(failure);
History list model declaration:
export default DS.Model.extend({
question: DS.attr('string'),
answer: DS.attr('string')
});
Ember-Cli-Mirage config.js end points:
this.get('/history-items', (schema) => {
return schema.historyItems.all();
});
Ember-Cli-Mirage fixture for history-items:
export default [
{id: 1, question: "1is this working?", answer: "Of course!"}
}
Error:
TypeError: internalModel.getRecord coming from record-array.js
This issue also happens when I try to create a save a record. The save is successful but when the model gets reloaded (and tries to reverse), it fails with the same error. It doesn't matter if I the fixture or not.
Controller:
var newHistoryItem = this.store.createRecord('history-item', {
question: question,
answer: answer
});
newHistoryItem.save().then(success).catch(failure);
The result returned from store.findAll and store.query is an AdapterPopulatedRecordArray (live array), mutation methods like addObject,addObjects,removeObject,removeObjects,
unshiftObject,unshiftObjects,pushObject,pushObjects,reverseObjects,setObjects,shiftObject,clear,popObject,removeAt,removeObject,removeObjects,insertAt should not be used.
Have a look at corresponding discussion and
Proposed PR to throw error and suggestions to use toArray() to copy array instead of mutating.
I think using toArray is fine, no need to reinvent the wheel. Even Ember's enumerable/array methods are implemented using toArray under the hood.
I like keeping transforms on controllers/components, so Routes are only concerned with [URL -> data] logic. I think here I would keep the model hook returning the server data, and use a computed property on the controller:
import Ember from 'ember';
export default Ember.Controller.extend({
reversedItems: Ember.computed('model.[]', function() {
return this.get('model').toArray().reverse();
})
});
Twiddle: https://ember-twiddle.com/6527ef6d5f617449b8780148e7afe595?openFiles=controllers.application.js%2C
You could also use the reverse helper from Ember Composable Helpers and do it in the template:
{{#each (reverse model) as |item|}}
...
{{/each}}
I would like to redefine my buildURL depending on what properties changed on the same model. For example, if the status changed, I would like to PUT to a certain route, and if the subuser changed, I would like to PUT to another route.
Example :
this.store.find('conversation', conv.id).then(function(conversation){
conversation.set('status', 'opened');
conversation.save();
});
This would use a certain PUT route and this :
this.store.find('conversation', this.get('selectedConv').id).then(function(conversation){
conversation.set('subuser', subuser);
conversation.set('url', subuser.get('email'));
conversation.save();
});
And this would use another PUT route even tho the changes are made on the same model. This is all happening in a controller.
You need to customize your conversation adapter, specifically the urlForUpdateRecord method.
The original method looks like this:
urlForUpdateRecord: function(id, modelName, snapshot) {
return this._buildURL(modelName, id);
},
In this method, you need to examine the snapshot and adjust the URL accordingly.
The latest version of Ember Data has introduced the changedAttributes property. This seems to be what you need.
Good luck!
I'm JS beginner and recently using ember.js for ui development I came across a problem that I can't solve.
I'm trying to reduce amount of posts to fit them on one page. Simply calling slice method on return value of this.get('store').find() doesn't work. I also tried to trim content of return value of all function, but still without success. Any ideas?
You can use the following:
App.YourController = Ember.ArrayController.extend({
arrangedContent: function() {
return this.get('content').slice(0 , 10);
}.property('content')
});
Where YourController is the controller that belongs to your route. So the content will be the resolved promise from this.store.find('modelName'). The arrangedContent property is the place where you modify the content when you want to perform filtering, ordering etc. Without changing the content directly and preserving all the data.
Give a look in that sample http://jsfiddle.net/marciojunior/pwQ5b/
If you really are calling slice on the return value of this.get('store').find you will run into trouble. The find function returns a promise and you need the result the promise resolves to.
You can solve this in one of two ways:
If you are using the standard Ember pattern for loading in your model, you should have a model function defined in your route. This will actually wait until the result is resolved and in your controller you can call slice on this.get('content')
If you are loading your data within the controller you will need to so something like below:
c = this
this.store.find('myModel').then(function(result) {
c.set('paginatedContent', result.slice(0, 10));
});
Here we are waiting for the promise returned by find to resolve to a result before setting the paginated content based on that result.
The cloudant RESTful API is fairly simple but doesn't match the way ember-data expects things to be. How can I customize or create an Adapter that deals with these issues...
In my specific case I only want to load records from one of several secondary indexes (ie. MapReduce fnctions).
The URL for this is below, where [name] and [view] would change depending on user selection or the route I am in.
https://[username].cloudant.com/[db_name]/_design/[name]/_view/[view]
Looking at the ember-data source there doesn't seem to be an easy way of defining URLs like this. I took a look at findQuery and it expects to send any variables through as url params not as part of the actual URL itself.
Am I missing something? Is there an obvious way of dealing with this?
Then the data comes back in a completely different format, is there a way to tell ember what this format is?
Thanks!
I had similar problem where URL's are dynamic. I ended up creating my own adapater by extending DS.RESTAdapter and overriding the default buildURL method. For example:
App.MyAdapter = DS.RESTAdapter.extend({
buildURL: function(record, suffix) {
var username, db_name, name, view;
// Do your magic and fill the variables
return 'https://'+username+'.cloudant.com/'+db_name+'/_design/'+name+'/_view/'+view;
}
});
I ended up also defining my own find, findAll, findQuery, createRecord, updateRecord, deleteRecord etc. methods as I had to pass more variables to buildURL method.
If returning data is in different format then you can also write your own serializer by extending DS.JSONSerializer and define your own extraction methods extract, extractMany etc.
You should evaluate how well your API follows the data format required by ember/data RESTAdapter. If it is very different then it's maybe better to use some other component for communication like ember-model, ember-restless, emu etc, as ember-data is not very flexible (see this blog post). You can also write your own ajax queries directly from routes model hooks without using ember-data or other components at all. It is not very hard to do that.