Ember binding properties to adjacent items in arraycontroller - ember.js

I'm new to ember and am still getting my sealegs under me with the framework. So far I think its great, except there is one thing that I can't seem to figure out: how to bind a property of an array item to a property of an adjacent array item.
Details:
My model is like so:
App.SRDate = Ember.Object.extend({
timeValue: null,
reductionAmount: null,
id: null,
index: null,
date: Ember.computed(function(){return formatted date as a string}).property('timeValue') ,
previousDate: ???
});
And I have a simple arraycontroller that just holds a list of the above objects. What I am trying to do is be able to call App.dates.objectAt(1).get('previousDate') and have it return App.dates.objectAt(0).get('date'). I got it to kind of work initially by using a computed property for the previousDate, but it would only update when I changed an item in bound object (i.e. if I changed the date for object 0 it wouldn't update in previousDate for object 1 until I changed the date in object 1, which caused ember to re-evaluate the computed property). If there is a way to define what objects a computed properties are associated with, then that would probably do the trick, however I don't think that is what computed properties are really supposed to be used for...
I also tried a binding like:
previousDateBinding: 'App.dates.getObject('+this.get('index')-1+').date'
but that didn't work either.
Thanks in advance for any help with this.

Just had a similar problem, here's how I solved it: http://jsfiddle.net/aMQU6/1/
In my ArrayController, I have a function that observes changes to my content. Then once it is added, I set a property in the new object to be the previous object, then bind the previous object's date to the previousDate property in the added object.
Hope this helped! Let me know if you have any questions.

Related

What is a proper way to handle temporary record states in Ember JS?

Let's say I have a list of records that are supposed to have state selected in some current context. I can see two approaches here:
Create an array of objects: records.map(record => { record: record, selected: false }), get and set like `recordObj.set('selected', true). Render this array.
Set property explicitly on record objects: record.set('selected', true). Render RecordArray.
The second one looks much easier than the first as you don't have to manage additional array and objects (this becomes a real hassle).
The problem is that if you set anything on a record object, even a field that is not present in the model, it will still persist through the application (I guess until the model is reloaded and new record object is created?).
How to prevent that and ensure that temporary properties will be clean on every route change? Or how to improve the first approach? Is there anything I'm missing?
In route file
actions: {
willTransition() {
let records = this.get('controller.records');
records.setEach('selected', false);
}
}
This will make sure every time you leave route you are safe.

After setting manually, computed property do not observe changes anymore

here is jsbin http://emberjs.jsbin.com/wumufifasu/edit?html,js,output with input where it is possible to enter id of the item and its text will be displayed nearby.
But there are two items with same "id" property and thus to display correct text they can be selected from the list on the bottom.
With "manual selection" itemId is changed only once - very first time , afterwards it's not set and subsequently "itemText" CP which observes itemId is not triggered.
The weird thing is that this effect is caused by setting value for "itemText" CP, and doesn't occur when this.setProperties({'itemId': item.id, 'itemText': item.text}); is replaced with this.setProperties('itemId', item.id);
Why's that?
A property cannot be both a computed property and also one that you set. If you set what was a computed property, the value you set destroys the computed property definition. The property becomes a simple value. You have to decide: do you want Ember to compute the property, or do you want to set it yourself?
(Actually, there is a notion of setting a computed property. But it requires writing the computed property definition in a special way. You don't need that here. If you interested in this feature, see http://guides.emberjs.com/v1.10.0/object-model/computed-properties/#toc_setting-computed-properties).
The problem is, you are using duplicate IDs--not a good idea. When the user clicks on one of the items, you pass the item itself as the parameter to the action, but from the ID of the item, since there are duplicate IDs, in the controller action you no longer have any way to find the correct item from the ID. I would suggest adding an index parameter to the loop, then passing that back as the action parameter.
By the way, why are you defining items in your component? It duplicates the items used as the model. You should pass the model into the component.
See http://emberjs.jsbin.com/likazerune/1/edit?html,js,output.
For ember 2/3 You can preserve computation by usng get and set method directly:
...
yourProperty: computed('propertyForComputation', {
get(key) {
// logic here
},
set(key, value) {
// logic here
},
}),
...

How to recompute an ArrayProxy when any property on an array of models is updated?

I have a simple ArrayProxy (computed) that I'd like to recompute when ANY property on the models inside the source (array) are updated. I need this because the filter_func I use to filter down the array could have any number of dynamic properties on it.
return Ember.ArrayProxy.extend({
source: undefined,
content: function () {
var filter_func = this.get("filter_func");
return this.get("source").filter(filter_func);
}.property("source.#each")
}).create({
filter_func: filter_func,
source: this.findAll()
});
Today I'm just using "source.#each" as shown above but this only forces the ArrayProxy to recompute when a new item is added/removed from the source array (not when I change properties on the actual models themselves)
Not sure if this will help, but as items are added or removed, maybe setup an observer on each property of the item being added. You would need to remove the observers on each item being removed from the array.
http://www.matchingnotes.com/ember-array-proxy/observers.html
That whole presentation is a fantastic walkthrough of ArrayProxy.
I'm going to give this a shot and see how it works. I'm very interested in the outcome of this question.

Resolving promises on filter

I have a filter and I can't get it to resolve the promise. It is using a model called 'patient' that hasMany 'addresses' and I want to filter out the address that has an addressType of 'Primary'. The filter seems to be working but only will return a promise. Thank you in advance for the help.
Patient controller
App.PatientController = Ember.ObjectController.extend
primaryAddress: Em.computed 'model.#each.addresses', ->
#get('model.addresses').then (addresses)->
addresses.filterBy 'addressType', 'Primary'
Solution (Thanks to #GJK)
primaryAddress: Em.computed 'model.addresses.#each', ->
#get('model.addresses').filterBy('addressType', 'Primary').get('firstObject')
The most obvious issue that I can see is that you're not watching the #each property of the addresses. Your property depends on model.#each.addresses, which means that your model is an array, and you're observing the addresses property on each item in that array. But you're not observing the content of the addresses array, just the array itself.
It seems to me that you should use model.addresses.#each as your dependent property. This will observe all addresses on a single model, including observing the contents of the addresses array (which updates when the promise resolves).
EDIT: Also, I didn't quite read far enough apparently. You shouldn't be calling then on the promise. Treat it as if it's already resolved, and it'll be updated when it does resolve. So use this instead:
App.PatientController = Ember.ObjectController.extend
primaryAddress: Em.computed 'model.addresses.#each', ->
#get('model.addresses').filterBy 'addressType', 'Primary'
The first time this property comptues, the promise will be unresolved so the filter won't return anything. But when the promise resolves, the property will update and the filter will work like you expect it to.

Where are Ember Data's commitDetails' created/updated/deleted OrderedSets set?

Ember Data's Adapter saves edited records in different groups of Ember.OrderedSets, namely: commitDetails.created, commitDetails.updated, and commitDetails.deleted.
model.save() from model controller's createRecord() will be placed in the commitDetails.created group. model.save() from model controller's acceptChanges will placed be in the commitDetails.updated group. But I can't find in code where the placement association happens.
I know that they are instantiated in Ember Transaction's commit function (which calls Adapter's commit, in turn calling Adapter's save). Throughout this process, I can't figure out where exactly the records are sorted according to the created/updated/deleted criteria.
I'm not quite clear what you're asking, but if you're looking for where records get added to their appropriate commitDetails set, I believe this is the line you're looking for, in the commitDetails property itself.
Here's the relevant code.
forEach(records, function(record) {
if(!get(record, 'isDirty')) return;
record.send('willCommit');
var adapter = store.adapterForType(record.constructor);
commitDetails.get(adapter)[get(record, 'dirtyType')].add(record);
});
Let's walk through it.
forEach(records, function(record) {
if(!get(record, 'isDirty')) return;
The above says, for each record in the transaction, if it's not dirty, ignore it.
record.send('willCommit');
Otherwise, update its state to inFlight.
var adapter = store.adapterForType(record.constructor);
Get the record's adapter.
commitDetails.get(adapter)
Look up the adapter's created/updated/deleted trio object, which was instantiated at the top of this method here. It's simply an object with the 3 properties created, updated, and deleted, whose values are empty OrderedSets.
[get(record, 'dirtyType')]
Get the appropriate OrderedSet from the object we just obtained. For example, if the record we're on has been updated, get(record, 'dirtyType') will return the string updated. The brackets are just standard JavaScript property lookup, and so it grabs the updated OrderedSet from our trio object in the previous step.
.add(record);
Finally, add the record to the OrderedSet. On subsequent iterations of the loop, we'll add other records of the same type, so all created records get added to one set, all updated records get added to another set, and all deleted records get added to the third set.
What we end up with at the end of the entire method and return from the property is a Map whose keys are adapters, and whose values are these objects with the 3 properties created, updated, and deleted. Each of those, in turn, are OrderedSets of all the records in the transaction that have been created for that adapter, updated for that adapter, and deleted for that adapter, respectively.
Notice that this computed property is marked volatile, so it will get recomputed each time that someone gets the commitDetails property.