Passing data from controller to model in emberjs - ember.js

I have a controller where I get the value from the hbs, which sends me the selected country value. I need this selected country in the model to compute and return back some results back to the hbs. How set this value in controller and get it in the model so I can compute using that value?

Well, there may be some different approaches to achieve this. However, I will give you some example which will hopefully help you.
//Controller.js
notes: Ember.computed('model.notes.[]', 'model.notes.#each.date', function() {
return this.get('model.notes').sortBy('date').reverse(); //This is an example of Computed function which in this case it's sorting notes based on date.
}),
blink: null,
actions: {
taskChangeColor: function() {
this.set('blink', 'blinker'); // this is another example that set new data by action which can be retrive from model and set to property
}
}
or another thing that you can do is to use Computed function in Model itself like
// model.js which is using ember-data and moment
timeZone: DS.attr(), //for example one property coming from server
utcOffsetFormat: Ember.computed(function() {
let time = moment.tz(this.get('timeZone')).format('hh:mm a');
return time;
// using a computed function to instantiate another value based on existing model property which means you can simpley use this property instead of direct one.
})
Additionally, you still are eligible to use action in Route.js instead of controller an example would be :
//route.js
actions: {
changeSave: function(step) {
var something = {
contact: this.currentModel,
};
this.currentModel.set('step', something.contact);
this.currentModel.save().then(d => {
// set your alert or whatever for success promise
return d;
}).catch(e => {
console.log(error(e.message));
return e;
});
},
in above example you can see that I have set an action to save notes in model which easily can set() to the model with exact same property name and if you do this you will get the result back immediately in your view.
hope it can help you. I recommend to read Ember-Docs

I would say, for your requirement you don't need controller properties for selectedCountryValue. You can keep this value in model itself.
In route,
setupController(model,transition){
this._super(...arguments); //this will set model property in controller.
Ember.set(model,'selectedCountryValue','US'); //you can set default value
}
and inside controller, you create computed property with dependent on model.selectedCountryValue. and compute some results
result:Ember.Computed('model.selectedCountryValue',function(){
//compute something return the result
}
In template, you can use {{model.selectedCountryValue}} directly.

Related

Ember computed property for retrieving one record out of a hasMany relationship?

Here's my situation, simplified:
// model/price-source.js
export default DS.Model.extend({
price: DS.attr('number'),
product: DS.belongsTo('product')
)};
// model/product.js
export default DS.Model.extend({
priceSources: DS.hasMany('price-source')
)};
In my products template, I want to be able to simply refer to the source with the lowest price, like so:
// templates/products.hbs
{{#each model as |product|}}
<span>{{product.cheapestSource.price}} €</span>
{{/each}}
How would I go about setting up the cheapestSource computed property? I imagine I'd have to do something like this:
// model/product.js
cheapestSource: Ember.computed('priceSources', function() {
let sources = this.get('priceSources');
let cheapest = sources.get('firstObject');
// iterate over sources and set cheapest to whichever has the lowest price
return cheapest;
})
The problem is, I have little idea how to loop through the hasMany relationship (apart from using the handlebars {{#each}} helper), and whether a computed property can even consist of a single Ember Data record from another model. Does sources.#each somehow play into this, if so, how?
Any help and ideas are appreciated, thanks.
I got it working by sorting the priceSources into a computed property sortedPrices, then calling the firstObject of the sortedPrices in the template. Will edit this post with the actual solution soon.
It took ages to test because I didn't realize that commenting out handlebars blocks will break the rendering of html inside them. Note to self...
EDIT: This did it:
export default DS.Model.extend({
priceSources: DS.hasMany('price-source'),
sortProperties: ['price:asc'],
sortedSources: Ember.computed.sort('priceSources', 'sortProperties')
});
Then in the template:
<span>{{product.sortedSources.firstObject.price}} €</span>
Works ok, without a ton of code.
This you can do on a controller where you need to use cheapestSource.
cheapestSource: Ember.computed('priceSources', function() {
let sources = this.get('priceSources');
let cheapest = sources.get('firstObject');
let array = sources.mapBy("price");
let min = array.reduce(function(a, b, i, array) {return Math.min(a,b)});
sources.forEach(function(source){
if (source.get("price") == min){
cheapest = source;
}
});
return cheapest;
})
Model is bit hard to achieve what you want this is one why using one computed and after template is render computed becomes object that you need.
cheapestSource: Ember.computed('priceSources', function() {
let product = this;
this.get('priceSources').then((sources)=>{
let array = sources.mapBy("price");
if(array.length>0){
let min = array.reduce(function(a, b, i, array) {return Math.min(a,b)});
sources.forEach(function(source){
if (source.get("price") == min){
product.set("cheapestSource", source);
}
});
}
});
})
When I have issues like this I use active model adapter on Rails and return for example cheapestSourcePrice as part of product in my custom serializer then in Ember product model just add cheapestSourcePrice and in template {{product.cheapestSourcePrice}} You dont want ember to do heavy lifting like this but if you dont control the server then do it like this. And one more thing after it sets source to cheapesetSource computed is no more until refresh. If you need it to stay computed you must add one more property on model then set him insted example
cheapestSource2: DS.attr()
this will allow for it to be an object
product.set("cheapestSource2", source);
and then in template
{{product.cheapestSource}}{{product.cheapestSource2.price}}
first property you call is there so computed is called.
If you got time try this solution too. this.get('priceSources') it returns Promise so you need to access resultant in then method and wrap it in DS.PromiseObject so that you can access it like normal object in template.
cheapestSource: Ember.computed('priceSources.#each.price', function() {
return DS.PromiseObject.create({
promise: this.get('priceSources').then(sources => {
let resultObj = {}
//sources is normal array you can apply your logic and set resultObj
return resultObj;
})
});
})

Accessing a model collection in controller - Ember2.5

When trying to access the model of a controller when creating a computed property on the controller, I get the following error:
model.uniqBy is not a function
app/controller/ticket.js
export default Ember.Controller.extend({
statuses: Ember.computed('model', function() {
var model = this.get('model');
return model
.uniqBy('status')
.map(function(i) { return i.status; })
.toArray();
}),
});
The model I'm giving to the controller is a collection returned from this.store.findAll('ticket');, but trying to iterate through it seems to be causing the above error. Is the collection given to the model not supposed to be an Ember.Enumerable object? Should I be trying to access the collection via the DS.Store (in which case I don't understand the need to pass a model to the controller)?
Ember.computed.uniqBy
A computed property which returns a new array with all the unique elements from an array, with uniqueness determined by specific key
Please try this instead for your computed property
statuses: Ember.computed.uniqBy('model', 'status')
EDIT
You can use ember computed map on this property to fine tune your array if needed, for example like this
status: Ember.computed.map('statuses', function(status, index)
return status.toUpperCase() + '!';
})
Another way is that computed property uses dynamic aggregate syntax as described here
https://guides.emberjs.com/v2.6.0/object-model/computed-properties-and-aggregate-data/
so Ember.computed('model.#each.status', function()
Hope it helps

Push record into local store and update template

I'm using this.store.push to push a record into the store from with the application controller (this action is being called from a socket service that is initialized in the application controller), using ember 2.2.1 I am achieving this like
var newStoreRecord = this.store.push({
data: {
id: id,
type: 'cart',
attributes: newCartItem
}
});
This adds this new item into the store but the template doesn't update to show the new item, I also tried adding something like this
this.get('cart.model').pushObject(newStoreRecord); assuming that I had something like cart: Ember.inject.controller(), at the top of the controller, might have had that one wrong anyway.
In the cart route I have my model being defined as so
model(params) {
this.set('routeParams',params.event_url);
return Ember.RSVP.hash({
event: null,
items: null
});
},
actions: {
didTransition() {
this.store.findRecord('event',this.get('routeParams')).then((result)=>{
this.controller.set('model.event',result);
});
this.controller.set('noItems',false);
this.store.query('cart',{auction_id:this.get('routeParams'),user:this.get('user.user.user_id'),combine:true}).then((result)=>{
if(!result.get('length')){
this.controller.set('noItems',true);
return null;
}else{
this.controller.set('model.items',result);
}
});
},
}
Not sure if I'm having troubles with getting the template to update because I'm not use the model hook? (btw, we're not using the model hook because of the bad performance on android we'd rather load an empty template with a loader and THEN load data rather than the other way around.
I have several thoughts here:
To answer your question specifically, when you set a variable from the store, like you're doing, it will only reference what was in the store at that time. It will not update automatically.
Your best bet is to add two new computed properties to your controller:
items: Ember.computed(function() {
return this.store.peekAll('cart');
}),
// You'll need to flesh this one out further
filteredItems: Ember.computed('items.#each.auction_id', function() {
return this.get('items').filter(...);
})
Reference filteredItems in your template and it should work.
Sidenote, I'd highly recommend refactoring a couple things.
I would use the setupController hook instead of didTransition. It runs after the model hook is complete so will be similar to what you're looking for
You can access the params at any time in the route, so you don't need to save them in the model hook
You don't need to return an a promise in the model hook if you're not doing any async data. Just return the object. You may need even need to do that.
Hope this helps.

Calling myModel.save() Returning Outdated Model

I am attempting to save an Ember Data DS.Model after it's been updated, but when I call myModel.save(), I'm finding that Ember Data is sending the original, non-updated model instead of the updated one. I'm trying to understand why this is happening and what I need to do differently.
Here are some details. First, I have two models:
/models/OrgUser.js:
DS.Model.extend({
...
orgPerson: DS.belongsTo('org-person', { inverse: 'org-user', async: true, embedded: 'always' }),
});
Note that I am using a customized RESTSerializer (see below), so the only use of embedded: 'always' is how my custom RESTSerializer handles it.
/models/OrgPerson.js:
DS.Model.extend({
...
orgUser: DS.belongsTo('org-user'),
})
To persist these models, I'm using the RESTAdapter. In an attempt to generate a single JSON request to my API that contains both models above, I've made a single customization to the adapter. I don't think this is affecting anything, but just in case I'm missing something, here it is:
/serializers/application.js:
DS.RESTSerializer.extend({
serializeBelongsTo: function(record, json, relationship) {
var key = relationship.key;
key = this.keyForRelationship ? this.keyForRelationship(key, 'belongsTo') : key;
var data = record.get('data');
if (relationship.options.embedded && relationship.options.embedded === 'always') {
json[key] = data[relationship.key] ? data[relationship.key].get('data') : null;
}
else {
json[key] = data[relationship.key] ? data[relationship.key].get('id') : null;
}
if (relationship.options.polymorphic) {
this.serializePolymorphicType(record, json, relationship);
}
}
})
With that setup, I have a template where I update the orgPerson properties. I can confirm these are bound properties because updating their input updates their display on another part of the template in real-time. I then call an action on my controller, and within that action do the following:
/controllers/my-page.js:
export default Ember.ObjectController.extend( FormMixin, {
actions: {
submitForm: function() {
...
this.get('model') // Chrome console shows that _data.orgPerson._data.firstName has the (incorrect) old property
this.get('model').serialize() // returns (incorrect) old firstName
this.get('orgPerson.firstName') // returns (correct) updated firstName
this.get('orgPerson').get('firstName') // returns (correct) updated firstName
...
}
}
});
Any idea why I am getting two different versions of the same model? How can I serialize the correctly updated model? Thanks for any input!
SOLUTION:
Thanks (again!) to #kingpin2k, I have resolved this issue. Here are the steps I took:
My serializer was in fact the problem, and using Ember's old preserved data. I replaced the line data[relationship.key].get('data') with the line data[relationship.key].serialize() and this was fixed.
I then ran into another issue, which was that if I edited my record, did NOT save it, and then went back to my list of records, the list still showed the edit. My first thought was that I needed to update my list page's array model to show only the latest content, but there didn't appear to be any Ember facilities for this.
So I ultimately solved this by using the following code in my route. Note that because orgPerson is async: true I had to wrap my model in a promise. Note also that I had to directly call model.orgPerson versus just model.
Updated route:
actions: {
willTransition: function( transition ) {
this.controller.get('model.orgPerson').then( function( value ) {
if ( value.get('isDirty') ) {
value.rollback();
}
});
}
}
Going forward, I just want to call this.controller.get('model').rollback(), so I'm going to write a util function that traverses eachRelationship and then individually calls rollback() on any of the objects. Whew, a lot of subtlety to get this working right.
Ember Data stores the original values in the data obj. It stores modified values in _attributes obj. During a save it moves _attributes obj to inFlightAttributes obj, then after the save is complete it merges them from inFlightAttributes to data. All of this is so you can rollback your record.
When you define a property as attr it hooks up the magical get where it first checks _attributes, then inFlightAttributes, then data and returns that property's result.
function getValue(record, key) {
if (record._attributes.hasOwnProperty(key)) {
return record._attributes[key];
} else if (record._inFlightAttributes.hasOwnProperty(key)) {
return record._inFlightAttributes[key];
} else {
return record._data[key];
}
}
https://github.com/emberjs/data/blob/v1.0.0-beta.8/packages/ember-data/lib/system/model/attributes.js#L267
In your case, Ember Data doesn't know you are saving that record, and you are manually grabbing the old properties from the data obj. You'd either need to manually merge _attributes to data or trick Ember Data into thinking you'd saved it.

Index view not refreshing after receiving updated data from backend

I am testing my application, so I am doing the following:
I show an index view (#/locators/index), of Locator objects, which I initially load with App.Locator.find();
I modify the backend manually
Manually (with a button/action) I trigger a refresh of the data in the ember frontend, without changing the route. I do this with App.Locator.find().then(function(recordArray) {recordArray.update();});. I see via console logging that a list request is sent to the backend, and that the up-to-date data is received. I assume this is used to update the store.
BUT: The view does not update itself to show this new data
Why does the view not get automatically updated when the store receives new data? Isn't that the whole point of the data binding in Ember?
If I now do the following:
Open any other route
Go back to the locators index route (#/locators/index)
Ember sends a new request to list the locators
The index view is shown, with the correct data (since it was already in the store?)
New data is received
(I am not 100% sure that 4 and 5 happen in that order, but I am quite certain)
So, my impression is that the data is properly updated in the store, but that somehow a full re-rendering of the view is needed to display this new data, for example by leaving and re-entering the route. Is this true? Can I force this re-rendering programmatically?
Ember changes view data when the underlying model is changed by the controller(Which is binded to the view)
(Only when the state of the application changes(url changes) router hooks are called)
Your problem could be solved when you do this.refesh() inside your route by capturing the action triggered by your view.
App.IndexRoute = Ember.Route.extend({
actions: {
dataChanged: function() {
this.refresh();
}
},
//rest of your code goes here
});
for this to work your handlebar template which modifies the data shoud have an action called dataChanged
example :
Assume this action is responsible for changing/modifying/deleting the underlying data
<button {{action 'dataChanged'}}> Change Data </button>
Refresh method actually does a model refresh and passes it to the corresponding controller which indeed changes the view.
There a couple of things that come to mind you could try:
If you are inside of an ArrayController force the content to be replaced with the new data:
this.replaceContent(0, recordArray.get('length'), recordArray);
Or try to call reload on every single record trough looping the recordArray:
App.Locator.find().then(function(recordArray) {
recordArray.forEach(function(index, record) {
record.reload();
}
}
And if the second approach works, you could also override the didLoad hook in your model class without having to loop over them one by one:
App.Locator = DS.Model.extend({
...
didLoad: function(){
this.reload();
}
});
If this works and you need this behaviour in more model classes consider creating a general mixin to use in more model classes:
App.AutoReloadMixin = Ember.Mixin.create({
didLoad: function() {
this._super();
this.reload();
}
});
App.Locator = DS.Model.extend(App.AutoReloadMixin, {
...
});
App.Phone = DS.Model.extend(App.AutoReloadMixin, {
...
});
Update in response to your answer
Handlebars.registerHelper is not binding aware, I'm sure this was causing your binding not to fire. You should have used Handlebars.registerBoundHelper or simply Handlebars.helper which is equivalent:
Handlebars.helper('grayOutIfUndef', function(property, txt_if_not_def) {
...
});
Hope this helps.
Somehow this seems to be due to the fact that I am using custom handlebar helpers, like the following:
Handlebars.registerHelper('grayOutIfUndef', function(property, txt_if_not_def) {
// HANDLEBARS passes a context object in txt_if_not_def if we do not give a default value
if (typeof txt_if_not_def !== 'string') { txt_if_not_def = DEFAULT_UNDEFINED_STR; }
// If property is not defined, we return the grayed out txt_if_not_def
var value = Ember.Handlebars.get(this, property);
if (!value) { value = App.grayOut(txt_if_not_def); }
return new Handlebars.SafeString(value);
});
Which I have been using like this:
{{grayOutIfUndef formattedStartnode}
Now I have moved to a view:
{{view App.NodeIconView nodeIdBinding="outputs.startnode"}}
Which is implemented like this:
App.NodeIconView = Ember.View.extend({
render: function(buffer) {
var nodeId = this.get('nodeId'), node, html;
if (nodeId) {
node = App.getNode(nodeId);
}
if (node) {
html = App.formattedLabel.call(node, true);
} else {
html = App.grayOut(UNDEFINED_NODE_NAME);
}
return buffer.push(html);
}
});
I am not sure why, but it seems the use of the custom handlebars helper breaks the property binding mechanism (maybe my implementation was wrong)