After setting manually, computed property do not observe changes anymore - ember.js

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
},
}),
...

Related

Set Style by Dynamic Actions does not work

I would like to change the color of the Display Only text on a form depending on its value. As I read it, I have created a Dynamic Action to set it, but it doesn't work. To test it, I created a second True Action in the same place that sets the value of another item. This works, so the Action fired, but the color did not change. I tried coloring by a static value (f.e. green), but nothing changed. I have an another simple test project where this method works.
The APEX version is 21.1.6
If you check the properties of the display only item in the inspector you'll see that the actual page item has an input tag with attribute "hidden" so it is not displayed. Following the hidden element there is another tag with an element id of item_name_DISPLAY. That is where you want to set the style.
Example:
Page item "P33_DISPLAY_ONLY" of type "Display Only"
Dynamic action on change of "P33_DISPLAY"
True action of type "Execute Javascript" with code
$('#P33_DISPLAY_ONLY_DISPLAY').css({ color: "red" });
Note the id: it is P33_DISPLAY_ONLY_DISPLAY not P33_DISPLAY_ONLY
Add a client-side condition to each true action per color you want to set.

How do I use a variable in an Ember component template that runs more than just once?

I need to generate a random string from two arrays with a bunch of names of people and devices. I have an Ember computed property that does it perfectly well. Except computed properties only happen once. So I end up with a list of like 5 rows with the SAME TEXT.
The reason I'm doing this is because I am getting a list of devices from a REST web service, but they don't have the ability to return the name of the device, only id and a bunch of other info. So I am supposed to use dummy names for now, and so I figure. no problem. I'll just generate a random name for each row. Well as I already said, I end up with this:
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
John's iPad <actual unique ID from the server>
When it needs to be the name of a random person and random device (which I have in arrays and the code works perfectly, but only executes once, and returns the same value for each consecutive time it's called.
So then I read on here and in an Ember book I have to do this:
property1: function() {
bunch of code
}.property('property2');
So this is supposed to run every time property2 changes, except. Back to square one. How do I change property2? I can't do it from the hbs template, from the code within the {{#each}} .. Then I read somewhere to use a custom helper, but in my experience ANY helper ecapsulates the returned value in a div which breaks the layout and would result in
text
helper response
remaining text
when what I want is:
text helper response remaining text
I mean yeah I could just make an array of text and then pass the index but then when I add new data I have to manually add items to the array because it's not dynamically generated for every row of data.
With my method I have like a ton of names and device names chosen randomly so no matter how much data is returned it can populate the name field until they fix it on the back end to return names.
Would really love to know how not just to solve this problem but how to run ANY CODE that I want from ANY PLACE I want in the page/template/etc. not just static properties or computed once properties..
sometimes you want to be able to have templates that use variables in them that are completely dynamic ran EVERY TIME that component is called.
Sometimes you want an actual helper that doesn't encapsulate the response in a div, so you can do stuff like The answer is {{answer-helper}}. and not have the output be this:
The answer is
5
.
Okay, first there are helpers if you just won't to output data. And the helper does not encapsulate anything in a div. And a helper is not a component. A component usually produces a div. But if you want a component that doesn't produce a div you can just set tagName to ''.
And thats what you should do: use two components:
device-list/template.hbs
{{#each devices as |device|}}
{{device-data device=device}}
{{/each}}
device-item/template.hbs
{{myprop}}
device-item/component.js
import Ember from 'ember';
export default Ember.Component.extend({
tagName: '',
myprop: Ember.computed('device.foo', {
get() {
return this.get('device.foo') + 'bla';
}
}),
});
now you have the property myprop once for every item in the array.
Another option is to have an array as computed property:
devicesWithNames: Ember.computed('devices.#each.foo', {
get() {
return this.get('devices').map(device => ({
foo: device.foo,
name: device.foo + 'bla', // anything you want to do
}));
}
}),
Another way to solve your problem is an ArrayProxy.
Would really love to know how not just to solve this problem but how to run ANY CODE that I want from ANY PLACE I want in the page/template/etc. not just static properties or computed once properties..
You can't, and thats good. This forces you to follow clean standards. For example you can't just call code on the component from the template. You can invoke helpers, or use data provided by the template. But this is a one-way data flow, and this helps a lot in understanding how a component works. If you need to do this use a computed property, and if you need this inside an {{#each}} loop write another component to be used inside the loop.
sometimes you want to be able to have templates that use variables in them that are completely dynamic ran EVERY TIME that component is called.
It seems you don't understand this. But this works as expected. A component that is called twice will compute all properties twice. However you need to understand that if you use an {{#each}} loop you are still in the same component, so where would you want to declare the property that should run for every instance in the array? Thats why you need another component for this. The other option is to have a computed property that does provide you with a new different array, with all the required data.

ComputedProperty on a component only updates the first time the watched property is set to a given value (when the property is a model property)

I seem to be having a bit of an issue with computed properties on ember 1.7.0 inside of a component. Let's say I have 2 models, A & B. A has a "belongsTo" relationships to B.
On my component template I have an Ember.Select control which will allow one to select a B entry for A.
{{view Ember.Select class="form-control" content=bList selection=objectA.objectB optionValuePath="content.id" optionLabelPath="content.name"}}
In my component I have a computed property which watches for changes on objectA.objectB like so:
isSomething: function() {
return this.get("objectA.objectB.id") === "some id";
}.property("objectA.objectB"),
Then in my component template I conditionally display something based on the value of isSomething:
{{#if isSomething}}
Something :D
{{/if}}
If I place a breakpoint in the isSomething computed property, and I select a new value on the select control, it hits the breakpoint as expected. It will hit again after I select a new value. But if I then select the original value again, the breakpoint will not hit (and the component will not rerender). It seems to only be hitting the first time I select any given value in the select control. At first I thought this may be an issue with caching, but adding volatile() to the property didn't seem to make a difference.
I can get around this by binding the select control selection to a property on the component like tempObjectB, and changing the property being monitored by isSomething to tempObjectB as follows:
isSomething: function() {
return this.get("tempObjectB") === "some id";
}.property("tempObjectB"),
Then to keep objectA's reference updated I can use a method which observes all changes to tempObjectB and updates objectA.objectB as follows:
updateObjectA: function() {
this.set("objectA.objectB", this.get("tempObjectB"));
}.observes("tempObjectB"),
These changes will allow the breakpoint to be hit every single time I change the value in the select control.
Does anyone know what could be causing this behaviour? I'd rather not resort to creating a temporary variable.
http://jsbin.com/qeyite/1/edit?html,js,output
http://jsbin.com/kugino/3/edit?html,js,output
The only difference between the two is that the first bin is storing and watching on objectA.objectB, while the second bin is storing and watching on tempObjectB.
For anyone who stumbles accross this, it appears to be fixed as of latest Ember.js release (1.8.1).
https://github.com/emberjs/ember.js/issues/5578

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.

Ember binding properties to adjacent items in arraycontroller

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.