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!
Related
I am new to ember, so please treat me like a fool. What I'm trying to do first is to understand the concept.
In my application I heavily rely on few jQuery plugins they fetch new portion of data in their callbacks, that's how these plugins are designed, but I am not sure how can I trigger them to fetch a new portion of data from API passing to API updated query parameters after plugin has been rendered.
I have wrapped the plugin in a component, in component's template I send data to it as (I use emblem.js syntax here)
= plotly-chart chartData=model
In model I have
//app/models/data-points.js
import DS from 'ember-data';
export default DS.Model.extend({
// time: DS.attr(),
ch1: DS.attr(),
ch2: DS.attr(),
ch3: DS.attr(),
temperature: DS.attr(),
});
And then in component itself I fetch data
//app/components/plotly-chart.js
dataPoints: Ember.computed.map('chartData', function(item){
return item.getProperties('ch1', 'ch2', 'ch3', 'temperature');
}),
and make some manipulations with data, which isn't so important for the question itself.
Ah, and I have a route graph/ which later calls that component
//app/routes/graph.js
import Ember from 'ember';
export default Ember.Route.extend({
queryParams: {
start_timestamp: {
refreshModel: true
},
end_timestamp: {
refreshModel: true
}
},
model(params) {
return this.get('store').query('data-point', params);
}
});
So as you see I have tried to fetch new properties via query params, finally it works great if I just update the url in browser, but now can I trigger new call to API and fetch new data and get this new data in a component itself?
Also I'm struggling to understand what role controllers play in all of these. It is mentioned that controllers will be deprecated soon, but still used here https://guides.emberjs.com/v2.10.0/routing/query-params/
My code seems to work without controllers, so this is really confusing.
Also I suspect maybe I should use services for what I'm trying to achieve, but not sure how.
Ember experts, could you please point me into a right direction? The most important thing is how to fetch new portion of data from API with updated query parameters (query parameters to API itself, not nessesarely the ember application, but I suspect in ember-data it is the same thing? or not %) %) %)).
UPDATE 1
Really I should use services for that, shouldn't I? Pass arguments into a service and query a store there. Would that be a correct pattern?
Then query parameters in url are not the same as querying the store and is an independent thing. Am I right?
but how can I trigger new call to API and fetch new data and get this new data in a component itself?
If you change your queryParam values in a controller using an action (combined with your current route setup) it will adjust your route and re-call your API, as the values are bound together to make this particular use case simple :-) You're about 98% of the way there ... :-)
Re controllers going away, they won't for a long time as the replacement hasn't been worked out yet. You could do some of this in a service if you want to, but there is no need as you are almost done.
Thanks, that make sense though. I just worried I'm doing it wrong.
By they way finally I found a way to access store from the controller Access store from component but:
1. I was unable to take out the data from that variable, probably it's me being stupid.
2. I double it's the right way to access store directly in a component and better to use services for that or rely on “Data Down Actions Up” (DDAU) paradigm?
Finally I was able to fetch new portion of a data calling a controller's action from within the controller, but then the next problem raised - the data was updated, but the JS code did not know about that because I feed the jQuery plugin with this data and it did not pick up changes automatically. I think I might be doing it a wrong way there %)
But finally I get it working by adding an Ember's observer to that variable and in observer calling a redraw function (for chart in this particular place).
#acorncom Thanks!
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.
Lets say I want that my page's title will change depending on a really simple field what is the Ember way of doing it?
I didn't really understand the bindings in Ember, do I have to create an object even if all I need is just 1 field?
Does Ember support two way bindings? if it does so how can I constrain the bindings to one-way only?
I think i'm a bit confused with Ember-data & regular Ember, when I use Ember-data do I need to care about bindings at all?
Thanks in advance :)
This is a little vague (or I just don't fully understand what you're asking), so I'll shotgun approach and we can narrow down as you ask more questions.
Preface: Ember Data is a client side record management library, Ember works completely fine without it.
Title
A page's title is a little tricky since it's kind of out of the scope of the viewable dom, but the best way to handle it would be with an observer. In the example below, as the title property changes inside of my application controller I'm setting the document.title.
App.ApplicationController = Em.Controller.extend({
title:undefined,
watchTitle: function(){
document.title = this.get('title');
}.observes('title')
})
Example: http://emberjs.jsbin.com/haducafu/1
Computed Properties
Ember does support one way bindings (though rarely do you need to care about bindings). More often you want to care about dependent properties. eg if property a has changed, property b should be updated etc. In the case below, b is a computed property that depends on a, if a changed, b is dirty, and ember should re-computed it.
App.ApplicationController = Em.Controller.extend({
a:'foo',
b: function(){
return 'Hello: ' + this.get('a');
}.property('a')
})
Example: http://jsbin.com/haducafu/2/edit
Simple Binding
Additionally Ember can do just simple bindings (you can actually skip defining name, since ember would define it the first time it uses it).
App.ApplicationController = Em.Controller.extend({
name:undefined
});
<h2>Hello {{name}}</h2>
Name: {{input value=name}}
Example: http://jsbin.com/haducafu/3/edit
One Way/Read Only:
One way will take the value from its host property, unless you set it, if you set it it stops following the dependent property and becomes its own (not modifying the dependent property).
Read only will take values form the host property, and if you try and set it it will blow chunks.
App.ApplicationController = Em.Controller.extend({
name:'billy',
oneWay: Em.computed.oneWay('name'),
readOnly: Em.computed.readOnly('name')
});
Try changing name first, they will all update, then change oneWay and it will diverge and never return, then change readOnly and it will throw errors.
Example: http://jsbin.com/haducafu/4/edit
What is the "appropriate" way in Ember to send a parameter from one route to another? For instance, I have two routes defined as such:
this.resource('activities', { path: '/activities/:on_date' }, function() {
this.route('new');
});
when on the ActivitiesRoute the user is presented with a dropdown of possible activities. When they choose something it transitions to the ActivitiesNewRoute:
this.transitionToRoute('activities.new');
and I know there is a second parameter available in the transitionToRoute(route,model) method but it's meant for passing in a model and I'm assuming this shouldn't be repurposed for other parameter passing. In this case the dropdown choice is picking an Action model id and the model for ActivitiesNew is a Activity.
Here are my three guesses at ways that might work:
1) Make it a router parameter
I supposed I could change ActivitiesNew to include a "parameter" as part of the route:
this.route('new', { path: '/new/:my_parameter' });
I'm not sure I'd really like to have it becoming part of the URL path but if this was the prevailing convention then I'd live with that.
2) Get a handle, post transition
Immediately following the transitionToRoute call I could set a property of the new controller class. Not sure if the controller would be setup yet but I'm imagining something like:
this.transitionToRoute('activities.new');
this.get('target').controllerFor('activities.new').set('my_parameter', myValue);
3) Use model parameter
this.transitionToRoute('activities.new',myValue);
I suspect that this is a major no-no. I haven't looked into the Ember code to know if this could work but it seems against convention so this is my "bad option".
transitionTo & transitionToRoute return a "promise-like" object. The parameter this object is resolved with is the route, from which you can access controller and currentModel. So a nice clean way to pass information to a route to which you are transitioning is:
var my_param = ....;
this.transitionToRoute('activities.new').then(function(newRoute) {
newRoute.currentModel.set('someProperty', my_param);
//or
newRoute.controller.set('someProperty', my_param);
});
EDIT/RANT:
note that in most cases, you do want to use needs, and bind things between controllers. However, there are certainly instances when you have things that depend on the logic of a route transition -- eg., controllerB has state X if we came to routeA from routeB, but state Y if we came from routeC. In that case, my answer is valuable.
The primary value of stack overflow to the development community is not the immediate answers you get to questions you post, but the massive ever growing wealth of googleable development knowledge. When you "infer" from a user's question that they "should" be doing something other than what they are asking how to do, you may be right (or you may be just incapable of imagining their particular circumstance), but if you answer only with your recommendation/rule/aphorism/cargo-cult-dictum instead of answering the ACTUAL QUESTION, you diminish the value of everybody else's google searches. If you want to tell someone to do something other than what they're asking, do it in a comment, or in a footnote to an answer to the actual question.
You can use the needs API (Read about it here):
App.ActivitiesNewController = Ember.ObjectController.extend({
needs: ['activities']
// Bind the property you need
actionTemplateBinding: 'controllers.activities.actionTemplate'
});
So what you actually need is to pass a parameter between controllers, which is exactly what needs is for. Plus, binding the property with needs you ensure it is in sync at all times, instead of relying on setupController being called.
You could use query-params (http://guides.emberjs.com/v1.10.0/routing/query-params/), as follows:
this.transitionToRoute('activities.new', {queryParams: {my_param: 'my_value'});
In order to be able to receive my_param in the new controller, you would also need to define the following lines:
App.ActivitiesNewController = Ember.ObjectController.extend({
queryParams: ['my_param'],
my_param: ''
...
});
A drawback of this solution is that the value of my_param will be serialized in URL - so it would not be suitable for some sensitive information you may want to pass between routes.
I'll answer my question with what I've decided to go with for now but keep it open for a a few days to see if anyone comes back with a more experienced answer. My answer may very well be perfectly fine ... it works anyway.
I've gone with a variation of #2 from the question. The difference is that that rather than trying to set a property in the ActivitiesNew controller from Activities controller I do the the opposite:
In ActivitiesNewRoute:
App.ActivitiesNewRoute = Ember.Route.extend({
model: function(params) {
return this.store.createRecord('activity');
},
setupController: function(controller,model) {
controller.set('actionTemplate', this.controllerFor('activities').get('actionTemplate'));
}
});
Still interested in hearing from people if there's a better way of doing this.
Transition to route with params and set model
yourAction:->
model = 'your-model'
route = 'your.path.to.toute'
routeLoad = #transitionToRoute route,
id: model.get 'id'
routeLoad.then (route) ->
route.set 'controller.model', model
return
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.