Ember Getting property from Controller in Model - ember.js

I have a computed property on a model and in order to compute that I need a property from a controller (not the one that is controlling the model).
I know there is needs: but this is just on
a controller level.
How could I get a property in Ember from a controller other than the one that is managing the model?
I'm trying to do some formatting like the person that [asked this question][1] but I didn't succeed what has been suggested there.
So I try to do the formatting on the model with a computed property, but to calculate that property I need another property from a controller.
Any help is greatly appreciated! Thanks!
Note: I'm using EmberData to manage the model.
Edit:
In order to clarify what I'm trying to do I have set up an example that shows the problem
in a general way: The example application lets you input numbers, store them,
and show them in a list. You can also input a "conversion factor" which doesn't change the model data itself but the presentation on the template. Say, you input the number 2, 2 gets saved on the model but when it is shown in the list it gets "formatted" with the conversion factor you entered previously and the calculated value is shown in the template. The problem is that the value with which I want to format is stored on a different controller. Here's what I have tried so far:
#1 Approach:
Computed Property on the ArrayController - using needs: in the controller to traverse and get the value
-->jsfiddle
Problems I have encountered:
The ArrayController seems to break and the template renders as if there are no stored records at all (Note: the example uses local storage, so create some records and uncomment the computed property on the ArrayController and you'll see it works originally as expected and shows the records you entered).
#2 Approach:
Computed Property on model itself
-->jsfiddle
Problems I have encountered:
I have no idea how I can get a property from a controller while beeing inside the model
#3 Approach:
Handlebars Helper and needs: on the controller
1) Define a computed property on the controller (that handles the model) to get the value in question from the other controller
2) create a handlebars helper and pass in the value from the model and the value from the controller and return the calculated value
-->jsfiddle (You can find the link for the 3rd jsfiddle in the comments since I don't have enough reputation points yet).
Problems I have encountered:
Instead of displaying the formatted number I get "NaN" on every value in the rendered template.
If anyone has an idea how to solve this or can point me into the right direction would be great. Your help is really appreciated! Thanks for your time!

Accessing any controller from a model is really going against the grain of Ember's architecture. Most formatting problems are best solved with a Handlebars helper, but if you need to combine data from the controller and model in a really serious way, then you probably want a computed property on the controller.
Can you give a concrete example of what you're trying to do? That will make it a lot easier to suggest the right solution.

I got it working: I've used the #3 Approach (outlined in my question).
I'm using a computed property to proxy the value from the other controller to the controller that is bound to the template where I want to display the formatted value
I created a handlebars helper called converted that will do the formatting. The handlebars helper accepts two parameters: the value that I proxy from the controller (which in turn comes from a different controller) and the value from the model.
The thing that didn't work previously was that in the template when using the helper I would get "NaN" on all items instead of the formatted output.
What solved the problem was that instead of lopping through the model in the template with:
{{#each controller}}
<tr>
<td>{{converted amount conversionFactor}}</td>
</tr>
{{else}}
<tr>
<td>No amounts here yet</td>
</tr>
{{/each}}
I changed it to this:
{{#each item in controller}}
<tr>
<td>{{converted item.amount conversionFactor}}</td>
</tr>
{{else}}
<tr>
<td>No amounts here yet</td>
</tr>
{{/each}}
and it works perfectly!
Here's the working jsfiddle

Related

Sorting Ember ArrayController when iterating the model

I have an ArrayController (documents) which displays a list of ObjectsControllers (document) for its content.
My ArrayController template (Documents):
{{#each document in model}}
{{render "document" document}}
{{/each}}
The issue I am facing is that the "sortProperties" and "sortAscending" properties on the ArrayController are no longer having any effect. I assume this is because I am looping "model". If I loop "each document in controller", the document ObjectControllers dont seem to get the model assigned to them as a call to .model then throws an undefined error. Should I be looping "controller" or "model"? If the answer is model, how can I sort it and if the answer is controller, how can I get the model set on each controller?
Rather than modifying the base model, the sorted collection is exposed as arrangedContent.
To explain why it's arrangedContent rather than arrangedModel, in Ember's ControllerMixin content is defined as an alias for model, but it used to be the other way around.
You may have to set the itemController property on your ArrayController (Documents).
See here: http://emberjs.com/api/classes/Ember.ArrayController.html
Edit: Also, you had it right the first time with {{#each document in controller}}. Doing it straight from the model will ignore configuration in your controller, which includes sorting rules you may have.

Difference Between Property and Observer?

Why would a computed property in a component not recognize a dependency changed within a view, but does recognize the change as an observer?
I tried two different approaches. The first one, a computed property that depended on the outside variable (projectId), only recognized the change some of the time. The other, an observer, sometimes recognized the change around the same time as the computed property, but also recognized it when the computed property did not. For example, it recognized both the property and observer when it inserted the element, only recognized the observer on a page transition within the same route, and only recognized the property on a page transition to a different route. Why is that? From what I understand, a dependency set on a computed property is similar to an observer, so why would it have trouble?
This is the component using the computed property:
import Ember from "ember";
export default Ember.Component.extend({
isFavorite: function() {
console.log("property");
// returns true or false
}.property("favorites", "projectId")
});
This is the component with an observer:
import Ember from "ember";
export default Ember.Component.extend({
isFavorite: null,
observesFavorite: function() {
console.log("observer");
// sets isFavorite to true or false
}.observes("favorites", "projectId")
});
Why do these two work differently? How would I be able to get the component to recognize the changes to favoriteId all of the time?
Edit
To clarify, the component worked with only a computed property when it was only present on the projects index page:
// app/templates/projects/index.hbs
<table>
{{#each sortedContent}}
<tr>
<td class="text-center">{{favorite-star action="updateFavorites" projectIdBinding="id" favoritesBinding="userFavorites"}}</td>
<td>{{name}}</td>
</tr>
{{/each}}
</table>
However, it stopped working when I added it to the project show page:
// app/templates/project/index.hbs
{{favorite-star action="updateFavorites" projectIdBinding="id" favoritesBinding="userFavorites"}} {{name}}
That was when I tried using the observer instead of the computed property to set the isFavorite property. However, when I tried that, it stopped working on the projects index page. Now, it only works when I have both the observer and the computed property, which I know is the incorrect way of creating this.
The only code I have for the component is in the app/components folder. It doesn't have a template in app/templates/components, and it worked just fine until I had to add it to the show template as well.
Why would this happen? Why would the computed property only work on projects index, and the observer only work on project show?
Edit Again
I've created two JSBins showing the issue. They're messy, but they work for this purpose. This is an example of it using a computed property to update the component and favorites array. It works normally on the index page, but breaks when the user tries to favorite or unfavorite it on the show pages. This is it using an observer, and works okay on the show pages, but not on the index.

Concurrent states with Ember.router

I'd like to implement this statechart in Ember.js v1.5.x using Ember.Router, but I have problems with the concurrency and history mechanism.
Basically when I activate the Summary route/state I'd like to transition to No changes made and Transient state in the same time. How can I achieve this?
P.S. I know that e.g. stativus have these capabilities but don't know how to use it with Ember.js routing. An example would bee good.
(image source: Ian Horrocks: Constructing the User Interface With Statecharts p.153).
:)
Yeah statecharts are lovely and all, but Ember actually affords sub-states through computed properties.
I'm not overly familiar with state charts, and I'd really need to consume the resources (horrocks) you mentioned here (https://github.com/emberjs/ember.js/issues/4767#issuecomment-41458710) before I'd be fully conversant in the nomenclature of that particular example (which I can do if you'd like).
To that end, and having said that, please take my answer with a grain of salt, because I may not fully understand the context. I just hope to help.
So in Ember you have routes. Those routes explain the interface of your application. These will effectively be your states. Routes are not your actions, or events. They provide a URL for your app to present itself to the world.
So, state A seems to be presenting the Students. You have two sub-states in there... 0 students and >0 students. You would handle these with the same Route (call it StudentsRoute), because they're both about the same set of data, just different substates of it. The route would have a path called /students probably. At that point, you'd have a controller gets fed a model by the router (the list of students), so to that end, this controller would be an extension of Em.ArrayController.
This array controller (auto-named StudentsController, extends Em.ArrayController) automatically has a 'model', and that model, once resolved, is the students "array".
In StudentsController, you could easily have a computed property called zeroCount which represents the state of zero or not about the model. Computed properties automatically stay up to date. That'd be defined like this:
App.StudentsController = Em.ArrayController.extend({
zeroCount: function() {
// zeroCount is true if zero, otherwise false
return this.get('model.length') == 0;
}.property('model.length');
});
In your students template, you could conditionally render one of two sub-templates depending on this zeroCount state... you'd do that like this:
{{#if zeroCount}}
{{partial "noStudents"}}
{{else}}
{{partial "someStudents"}}
{{/if}}
Mind you, for this example, that'd be somewhat overkill... you probably don't need to render other templates (partials) like that.. there's an easier simpler way to do it because this is a common pattern in ember (rendering a list, and optionally rendering something else if there are no items in it, without needing the zeroCount property).
{{#each model}}
<p>This renders against each student... <br>
so if your students have a property called name, <br>
then you could just write {{name}} and it'd render the
students name</p>
{{else}}
<p>This renders when there are no students</p>
{{/each}}
You'd put a delete link on each of those items... and the live bound properties handle all the states for you... (thus, when model has zero items in it, the template goes into the else block of the each... otherwise it goes into the main block).
The delete action, handled by something like Delete inside your #each model template (handlebars) directive goes to the controller and looks for an action inside of it called, unsurprisingly, delete... and that'd look like this:
App.StudentsController = Em.ArrayController.extend({
actions: {
delete: function(itemToDelete) {
itemToDelete.delete();
// assuming the model "class" understands delete
}
}
});
The edit state would have its own route... possibly a nested route on the students, called edit, possibly not depending on if you wanted the list to appear on the screen while the edit page appears...
The "changes made" state is effectively handled not on the route, but on the model... as it should be... the model is responsible for persisting the object graph, or telling the view and controller whether or not the model has changed (Ember Data, for example, afford isDirty as a state on each model instance that can tell you whether it has changed or not)...
Hopefully this whets your appetite. I recommend going through some of the examples on the Ember site... they really do help, and following the Ember TODOMVC app if you haven't checked that out...
Ember thrives on these kind of flow-based state driven UIs... check out Tom and Yehuda's keynote at confreaks if you haven't already... they talk about flows in exactly the same way you're talking about these states and sub-states.
Hope that helps.

Ember.js: {{render}} does set 'content', but does not set 'model'

From my main handlebars dashboard template I am rendering multiple templates in a loop, setting the model as following
{{#each forecastDispatch in forecastDispatches}}
{{render "balance.forecastDispatch" forecastDispatch}}
{{/each}}
The dashboard combines multiple models and renders them on the same page/route.
From logging to the console, I can see that the BalanceForecastDispatchController controller gets instantiated correctly for each render call and the forecastDispatch model of type DispatchType is set as content, but not as model, model is still undefined. As the model is not populated properly, passing data to lower level components does not work. I thought that the model is just an alias/proxy for content, hence I am quite surprised.
What am I missing here? Any help is really appreciated, I am trying to solve this for quite some time now, but cannot find the culprit.
The issue was caused by snippets in my Handlebars template which were not commented out properly. As a result, one extra instance of the BalanceForecastDispatchController was created by Ember. This controller had off course no model/content assigned to it. In the Chrome Ember-inspector the controller was shown without a model.

Generating a loop counter in Ember.js #each

I'm finding the restrictions of handlebar templates in Ember.js to be absolutely crippling. You can't execute arbitrary javascript. All you get is a fixed set of abilities (if/else, each, action, view, outlet) and the ability to output the value of a property on the current context object.
Which means I cannot figure out how to do something as simple as utilizing a loop counter in order to, for example, slap positional IDs on an html element inside a loop based on it's position in the collection I'm iterating on.
It doesn't make sense to add it as a computed property on the model, because that's not model-level knowledge (the same model could be in two different positions in different views).
FYI-Using Ember-1.0-pre, Ember-Data, and Rails with ActiveModel::Serializers on PostgreSQL.
With handlebars.js (v. 1.0.rc1) I could do this:
{{#each links}}
<li>{{#index}} - {{url}}</li>
{{/each}}
I'd recommend writing a handlebars helper, in which you can execute arbitrary javascript. See
http://handlebarsjs.com for some examples.
Alternatively, you could include the positional IDs on your child views (not your models).
Turns out providing a loop counter is the exact subject of the final example on this page:
http://handlebarsjs.com/block_helpers.html
(Dan G already pointed out the "writing a helper" solution in his answer, but since there's already a published example, I wanted to create a fresh answer to make the link more prominent.)