Ember.js 2.3 implement #each.property observer on a HasMany relationship? - ember.js

Say I have a hasMany relationship Procedure => hasMany Steps, with async:true, and I have a Procedure Component (on the procedure route) called procedure-main, which lists the steps as so:
{{#each steps as |step| }}
{{step.title}}
{{/each}}
I need to observe a property on each step (say, stepStatus) on change to the stepStatus on any of the steps. In Ember 1.7, I had something like this on the procedure controller:
stepsStatusObserver: function(){
...
}.observes('steps.#each.stepStatus')
This was fired on change of stepStatus on any on the steps, and whatever I had in this function was fired whenever the status changed as well. However, in Ember 2.3, I cannot implement this. I have tried with
stepsStatusObserver: Ember.observer('steps.[].stepStatus', function(){
...
})
but this only fires once when the steps are being listed on the page. When I change the status of one step to a new value, the function is never fired.
How can I replicate this functionality in Ember 2.3?
Note: In my use case, I cannot rely on manually setting off the function inside the observer on the click of a button as it must automatically fire if the stepStatus property was changed on any step.

The problem is that steps.[].stepStatus is not a valid dependent key anymore. You should replace it by steps.#each.stepStatus.
Here is a summary of valid and invalid dependent keys in current Ember versions:
array - this observes if the array reference itself changes, e.g replacing the entire array with another value or array such as that oldValue !== newValue.
array.[] - this observes both when the array itself changes (above) and when the array length changes (functionally equivalent of array.length)
array.#each.property - observes both cases above and when property of some of the array's items change
array.#each.{prop,anotherProp} - you can also observe multiple properties while specifying only one key. This expands to array.#each.prop and array.#each.anotherProp.
array.#each - isn't valid anymore, no trailing #each. Use .[] instead.
array.#each.property.property - Also not valid. Note that #each only works one level deep. You cannot use nested forms like todos.#each.owner.name or todos.#each.owner.#each.name.
array.[].property - not valid anymore. Use #each form instead.

You should still use #each
stepsStatusObserver: Ember.observer('steps.#each.stepStatus', function(){
...
})

Related

Select an input after its inserted using `Ember.run`

Updated — The problem with my solution below was with if statement around Ember.run. Removing it solved the problem, but my approach was also incorrect. Instead of scheduling the focus for after rendering, I should make a custom component as acorncom suggested.
Original Question:
I'm building a list of text inputs based on an array on my model. On newline I'd like to insert a new text field and then select it. I'm trying to do this with Ember.run.scheduleOnce, but the function I provide is never called.
I've reopened the textfield to expose a data-index attribute binding, and in my template am rendering:
{{#each model.entries as |entry index|}}
{{input value=entry.text class='field--entry' data-index=index insert-newline=(action 'insertEntry' index)}}
{{/each}}
In my controller:
actions: {
insertEntry(index){
// This works fine and a new textfield is added.
this.get('model.entries').addObject({text: 'Example'});
if (index){
Ember.run.scheduleOnce('afterRender', this, function(){
// This doesn't seem to work at all.
console.log("This is never logged");
$(".field--entry[data-index='" + index + 1 + "']").focus();
});
}
}
}
Per a work-in-progress branch here (https://github.com/ember-best-practices/recommendations/blob/initial-pass/guides/run.md), you generally want to steer clear of using afterRender if there are other approaches that would work better.
I'd suggest using a component to handle rendering your list of items, as you'd be able to use the component lifecycle hooks to do what you're after. If you check the guides (specifically the section discussing didRender), you'll see an example that closely matches what you're after ... https://guides.emberjs.com/v2.17.0/components/the-component-lifecycle/#toc_making-updates-to-the-rendered-dom-with-code-didrender-code
I don't think you need to manually manipulate the focus:
This Ember can set the autofocus attritbute on render.
"(eq index 0)" sets it to true on the first element only
{{#each model.entries as |entry index|}}
{{input value=entry.text autofocus=(eq index 0)}}
{{/each}}
There was a syntactic mistake in your template and I fixed it. I suspect that in your case the action was never called somehow. Can you check the twiddle I prepared for you? What it does is simply adding a new input and giving focus to it. I just changed the selection logic for jquery focusing to
Ember.$(`.field--entry:eq(${index+1})`).focus();
and it simply works as expected.

how to prevent sync of nested array object between controller and component in ember

I am working on Ember.js 2.12.0
How to prevent sync of nested array object between controller and component.
For ember string its working but for nested array object its not working.
below is ember twiddle link
ember twiddle
In twiddle if you change the value of String and arr and click to HOME route and again come to ABOUT route then you can see that value of String gets changed but the value of arr is not gets changed
Thanks in advance for help.
String is a primitive typed object, but Array is not. So when the component modifies the str, it doesn't reflect to the controller's str. Because you are copying its values in component. You can try this by adding {{str}} to the about.hbs. See it doesn't changed. (In your current twiddle.)
But the array is different. It's not just a value. Component's arr is the same with the controllers arr. When the component modifies it, you can see the change it in about.hbs. (Also refer to the pass by value, pass by reference.)
On the other hand, controllers are singleton, they stores the last values on them. So whenever you changed the array, you can see it is persistent.
Above, I mentioned about causes. Below I'll mention about solutions:
Alternative 1, clone the array before sending it to the component.
Alternative 2, reset the array in route's specific hooks.
Alternative 3, use model hook to generate and send the array.
I've also modified your component, see in this twiddle.
Further, never define an array in an object definition. Because all the instances of that class will share the same array instance. (It is also written in somewhere at the guide.)
import Ember from 'ember';
export default Ember.Route.extend({
setupController(controller, model){
controller.set("arr",[{name:'ravi kumar'}])
}
});
this will make sure you always get arr reset.
this file you create in routes/about.js

Emberjs one way & two way bindings?

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

How to access deleted records in ember?

Assume I have a tasks application.
When the user updates a task an update button is enabled and the number of updated records is displayed. I do not manage to implement this when I delete a record.
For updates this is easily done in the controller:
App.TasksController = Ember.ArrayController.extend({
changedAmount: function(){
#filterBy('isDirty', true).get('length');
}.property('content.#each.isDirty');
})
But then when I delete a record using the deleteRecord function I don't manage to get the amount of changed/deleted records. Example:
App.TasksRoute = Ember.Route.extend({
.
.
actions: {
delete: function(task){
task.deleteRecord()
}
}
});
How can I access the deleted records or at least the amount of them?
My environment:
Ember : 1.3.1.1
Ember Data : 1.0.0-beta.6
Handlebars : 1.2.1
jQuery : 1.10.2
Looks like the property you're looking for is isDeleted. So you could modify that controller to be like:
App.TasksController = Ember.ArrayController.extend({
dirtyTasks: Ember.computed.filterBy('content', 'isDirty', true),
deletedTasks: Ember.computed.filterBy('content', 'isDeleted', true),
changedTasks: Ember.computed.union('dirtyTasks', 'deletedTasks')
});
Update:
I see what you mean about the content being autoupdated. I tracked this one down to the recordArrayManager being used by the Ember Store. When you issue either a find or a filter on the store, what is returned is a filtered record array, and one of the filters applied to that array is that none of the records in it can be in an isDeleted state. You can see that in action here.
Off the top of my head, I can't see a solution to this that I'm particularly satisfied with. But, if you absolutely need the content in your array controller to included deletedRecords, then you would need to override the method I linked to above in a custom RecordArrayManager class, and then set that recordArrayManager in the initialize for your application's store, as seen here.
Here's a jsbin demonstrating it
Note that this is a dirty hack and you really shouldn't do it. Frankly, I think a better solution would be to, instead of deleting the record using ember data methods, come up with your own flagged customIsDeleted state and set the model in that state instead of outright deleting it, if you really want it to stay on the screen.

ember js compare values in DOM if statement or at least in a View with value from DOM

I been trying to compare some values in handlebars if statement {{#if value == 'otherValue'}}, obviously unsuccessfully because handlebars do not like this and expecting a string, boolean, or function name. Well that would be ok, but then I tried to pass parameter in the function like you can do with {{action}} helper, and well that didn't workout either, got this in console
Error: assertion failed: You must pass exactly one argument to the if helper
So then I decided to do this in a View, even so ember js guides points that accessing template values in-scope is unusual and they provide only poor paragraph with no examples.
http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_accessing-template-variables-from-views
So when I tried to do this, I got a problem of accessing those variables, I tried this way this.get('controller.templateVariables') and via full path to View, but value was either undefined or .get() wasn't exists as a method.
So at this moment I decided to save variable in the DOM data property, but turns out this {{#view App.TabsView data-title="{{tab}}"}} is going to literately give me a string {{tab}} when I try to access it from View with this.get('data-title').
The only way left to me was to insert additional element inside view and store variable there, and afterwards access it with jQuery class selector. but element is not yet exist in the DOM at the time of isVisible function gets executed, so I have no access to values at that time. That explains why this.get('element') was returning null.
Similar examples on ember js mostly ends up with something like if (someLogic) {}, but how I can do any logic when there is no variables available to me.
Question
To simplify my question - is there a way how I can do such a thing in ember js? Simple as
// have objects stored in controller
var obj = [{title:'Tab1'}, {title:'Tab2'}, {title:'Tab3'}];
// loop via them in the DOM
obj.forEach(function(tab) {
// do this kind of comparison
if( tab.title == currentTab() ) {
// do something here
}
});
If that is not possible, then what would be the other way to achieve similar functionality?
You can write a handlerbar helper to do this
{{activeTab tab}}
Handlebars.registerHelper('activeTab', function(tab) {
})
See a question about the same issue
Active Tab
Or look at existing helpers to write your own
Bind Helper
Template Helper
I think the best way for me to demonstrate this is with a heavily commented JSFiddle that I've put together for you: http://jsfiddle.net/PbLnm/
Please ask any questions below if you're not sure about anything.
The main part which determines when to add the active class is in the computed property:
// Determine if the object we have for this view is the same as the activeTab's object. If it is the same, then this view is the current active tab.
active: function() {
return Boolean(this.get('parentView.activeTab') == this.get('tab'));
}.property('parentView.activeTab')