Nested Views in Ember - ember.js

I've got the need for a recursive nested set of views in Ember. Basically think of it as a file structure with folders.
I thought I could do it with render, but that didn't work. Inside structures/show view:
{{#each child in structures}}
{{ render 'structures/show' child }}
{{/each}}
I get the error:
Uncaught Error: assertion failed: This view is already rendered
I read in another SO Question about using the {{view}} helper:
{{#each child in structure.structures}}
{{ view App.StructuresShowView structureBinding='child'}}<br>
{{/each}}
But this doesn't render properly. Possibly because the example is too old?
How do I properly render the same view (and controller?) with a different object in order to build a nested/recursive view? Is there a better way than the object directions?
Thanks.
Update. Looks like itemControllerClass might be what I'm looking for? Still trying to figure it out.

I think you cannot call {{render}} multiple times on a single route. If you need to create something many times, you probably need to use {{control}}, which is the same as {{render}} with a difference that {{render}} has one controller (singelton) and {{control}} has a new controller every time it's called.
Here's an open issue related to your question.

Related

Handlebars debug missing {{property}} of model

I would like to see in the console some kind of output if a template doesn't get all {{properties}} (for debugging)
e.g. If a template
<script type="text/x-handlebars" id="demo">
<h1>{{name}}</h1>
<p>{{desc}}</p>
</script>
is called with a model
{
name:"Max",
}
there should be an output like console.warn
Template "demo" could not find property "desc"
Is there any helper, callback, ... that is called every time a Handlebars expression like {{property}} is called?
You can do whatever you like with javascript. But do it carefully. :)
There are many way to achieve your goal depending on what you really need and how many time you can spend on hacking ember/handlebars/ember-data packages.
First and easiest variant is to override render method of SimpleHandlebarsView/_HandlebarsBoundView, which displays simple binded values (there are few other views: for #each, with, etc). This is simple sequence: override method, call an old method, check the result of getting value for rendering, console.warn if result === undefined. You can't get template name inside that method, but you can get parent view for rendered value. So, I suggest to turn on LOG_VIEW_LOOKUPS to easily match views with templates.
Example of console logs:
Rendering application with default view <(subclass of Ember.View):ember226> Object {fullName: "view:application"}
Rendering index with default view <(subclass of Ember.View):ember251> Object {fullName: "view:index"}
Rendering <(subclass of Ember.View):ember251> , property is undefined: model.undefined
JSBin with example.
Another way is to override handlebarsGet method from ember-handlebars-ext package: https://github.com/emberjs/ember.js/blob/v1.7.0/packages/ember-handlebars/lib/ext.js#L75.
Third, if you need to catch accessing to undefined properties of Ember.Objects, you can override unknownProperty method for those objects.
If you need to check a rendered data quickly, you can use {{log model}} method in Handlebar's template.
You can get deeper into patching and rewriting core of Ember, of course. But I advise not to rely much on inner structure of libraries. Good luck!
No, they use Ember.get(context, propertyName) There is no metadata returned saying the property is defined vs undefined.

How to prevent views from being destroyed in Ember.js

Quick note:
I don't believe this is a duplicate of Ember.js: Prevent destroying of views. Other related questions that I've found are out-of-date.
In case this becomes out-of-date later, I am using Ember 1.7.0 with Handlebars 1.3.0.
Context for the question:
As the title states, I am wondering how to transition between views without destroying them. Using queryParams does not solve my issue.
I am creating a calculator with the following nested views:
>>Calculator View
>>Report View (hasMany relationship to Calculator)
--School Partial (I am using queryParams here)
I am able to navigate between the Report views just fine without destroying my School partial, since I am using queryParams and using a displaySchoolPartial boolean to show/hide the partial. Example below:
Report template (stripped to only show the essential part):
<script type="text/x-handlebars" data-template-name="calculator/report">
...
{{#link-to "calculator.report" (query-parameters displaySchoolPartial="true")}}
{{render "_school"}}
</script>
School template (also stripped down):
<script type="text/x-handlebars" data-template-name="_school">
{{#with controllers.calculatorReport}}
<div {{bind-attr class=":schoolPartialWrapper displaySchoolPartial::hide-element"}}>
...
</div>
{{/with}}
</script>
This works as expected. Navigating between different Report views and School partials, as stated before, does not destroy the view.
The problem:
My problem comes when navigating to the Calculator view, the Report view is destroyed, which then destroys my School view. I do not want to also use queryParams to replace my Report views.
The reason I need to make sure the views aren't destroyed is because I have a select box with 3,000 schools in my School partial. It takes too long to re-render this. It would be a much better UX to simply show/hide the Report views.
Don't fight with Ember. You will lose.
Views are instantiated and rendered when needed and torn down when done.
Why do you have a 3000-element dropdown, anyway?
If you really, really want to do this, what I would suggest is putting a {{render}} on your application page, and hide it. The view will be created and rendered when the app comes up and persist as long as the app is alive. Then, in the didInsertElement of your view, do a cloneNode of that hidden element and insert it into the view's DOM somewhere. You may have to muck around getting event handlers wired up correctly.
My suggestion is not using "render" but using "partial", so you only need to drop in the template that you want. Have a control variable that set show/hide via css class. And control that variable using you controllers.
Using "partial" will allow you to have school template independent from report, thereby removing report will not affect school.
Just make sure you define the outlet and partial correctly.
Hope it 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.

Ember JS - Reference view inside linkTo

I have an Ember JS confusion. First of all I have to admit I'm a bit of an Ember newbie so apologies if this is obvious.
I have the following template:
<script type="text/x-handlebars" data-template-name="threadListItem">
{{#linkTo show thread.receiverLogin}}
<h4>{{thread.receiver.login}}</h4>
<span>{{view.prettyTime}}</span>
{{/linkTo}}
</script>
There's basically a linkTo helper with some elements inside of it.
The weird thing is the {{view.prettyTime}} doesn't display anything when it's inside the linkTo.
However, if I move the span outside the linkTo it finds the view variable ok and it works.
Is there anyway to get access to the view object inside the linkTo?
Christopher is correct that you can access the prettyTime property with:
{{view.parentView.prettyTime}}
Since you're new to ember, it's maybe worth mentioning that in most cases it is best to avoid accessing properties of the view. Instead, consider adding a prettyTime property to your controller. The controller will be the default context for sub-views like the one created by the linkTo helper, so you'll be able to access it with just
{{prettyTime}}
linkTo creates its own view, so the view you want to get access to is the parentView of view.
{{view.parentView.prettyTime}}

Instantiate a controller class using the {{view}} helper?

While connectOutlet("basename") automatically creates an instance of BasenameController, I was wondering if there's a way to do the same using the {{view}}-helper.
I have tried several things I've seen in examples, but non of them seem to work:
{{view controllerBinding=App.BasenameController}}
{{view controllerBinding=App.basenameController}}
{{view controllerBinding="App.BasenameController"}}
{{view controllerBinding="App.basenameController"}}
I have also tried to do the same using controller instead of controllerBinding, unfortunately without success, and I was also unable to find out where exactly the difference is between the two of them.
Does anybody know how to achieve my goal?
You probably want to use an outlet. The connectOutlet/outlet functions are meant for rending other controller/view pairs.
Lets say we have a person view, but inside that view we want to have another controller/view pair. For this, we need to use a named outlet, our template would look like this.
Person View!
{{name}} = the person's name!
{{controller}} = PersonController!
{{outlet other}} = our outlet
Then inside the router when you want to attach another controller/view to that outlet you can simple use connectOutlet on the personController.
router.get('personController').connectOutlet('other', 'other');
That will wire OtherController and OtherView together and display them in your template. The first param is the outlet name, the 2nd is the controller/view.
This allows you to easily swap different controllers and views onto that outlet. For example, using a different connectOutlet api, we could
router.get('personController').connectOutlet({
outletName: 'other',
controller: router.get('carsController'),
viewClass: App.CarsView
});
...
Btw, to answer you original question. You can get access to other controllers from your view by doing this: {{view controllerBinding="controller.target.otherController"}}. Every controller will have a target property that points back to the router. However, I do not recommend using this code. It's brittle, hard to test, hard to debug, and will come back and bite you in the future.