Ember JS Dynamically render template based on content in model - ember.js

I am trying to programmatically render different templates into a named outlet based on a specific value in my model.
Here are two JSBin examples:
This one shows the basic structure but the specific render code is commented out.
http://jsbin.com/OhegexO/1/
But when I try to use the renderTemplate method in my route it doesn't work
http://jsbin.com/OhegexO/2/
I see the following errors in my console
Error while loading route: TypeError {}
Uncaught TypeError: Cannot call method 'connectOutlet' of undefined
I can seem to figure this out. The use case is that I want to use different templates based on some parameter that will be in my model.

Admittedly I'm not very familiar with rendering into named outlets, but it appears it won't render into an outlet that hasn't been rendered yet. That being said it's possible instead of doing it from renderTemplate, you could allow the product template to render normally (don't override renderTemplate), then render the edit form delayed, into the product template afterward (see 2nd jsbin). That's a little awkward, so if you wanna hijack the renderTemplate for the sake of it, see this first jsbin. They both involve waiting for the product template to render, then rendering it.
And I believe if you override the renderTemplate hook, it skips the default rendering, hence never rendering the product template. After further investigation, this is true, if you super it, it works as well. BTW this._super() says run the default implementation of this method as well.
http://jsbin.com/ujiKire/5/edit
Using setup controller:
http://jsbin.com/OvONejo/1/edit
setupController: function(controller, model){
var templateEditForm = model.get('editform');
var templateInto = 'product';
var templateOutlet = 'editform';
console.log("render the [%s] form into the [%s] template, using the [%s] outlet",templateEditForm, templateInto, templateOutlet);
// Why does this code not work
var self = this;
Ember.run.later(function(){
self.render(templateEditForm, { // the template to render
into: 'product', // the template to render into
outlet: templateOutlet, // the name of the outlet in that template
controller: controller // the controller to use for the template
});
},1);
}

Related

Ember can't find my template when using the renderTemplate hook

I can’t figure out why Ember can’t find and render the template specified in my renderTemplate hook override. I have the following route configuration as per router.js:
this.route('posts', function() {
this.route('history', function() {
this.route('new', { path: '/new/:id'});
});
});
I’m also making use of my history index route. Therefore, my history.hbs is just an outlet, and all my templating for this route is actually housed in index.hbs. This way, I can render my new.hbs template on its own page.
In new.hbs, I’d like to render an outlet, in addition to some other content. To handle this, I attempted to override the renderTemplate hook in routes/new.js, like this:
renderTemplate() {
this.render('posts/history/test', {
into: 'new',
outlet: 'test',
});
}
I created the new template under templates/posts/history:test.hbs`. File structure for my templates folder looks like this:
templates > posts > history > new.hbs, test.hbs
Finally, in new.hbs, I added my outlet for this new template:
{{outlet 'test'}}
When I navigate to my new route, I get the following error:
Assertion Failed: You attempted to render into ’new' but it was not found. Anyone know how I can get past this?
I believe you can only render into an ancestor route.

How do I set a class on a parent element when a route is the first one loaded?

I have an Ember demo app that works fine if the first route loaded is 'index', 'list' or 'list/index', but not if the first route loaded is 'list/show'. Code is at https://github.com/DougReeder/beta-list , demo is running at https://ember-demo.surge.sh To see the problem, set your window narrower than 640px and surf to https://ember-demo.surge.sh/list/5 You'll see the list panel, rather than the detail panel.
The underlying problem is that, when the route is 'list/show', the divs with class 'panelList' and 'panelDetail' should also have the class 'right'.
I can't set this in the template, because panelList and panelDetail are created by the parent 'list' template. If I move panelList and panelDetail to the child templates 'list/index' and 'list/show', then the list gets re-rendered when going from 'list/index' to 'list/show' which would be a terrible performance hit.
Currently, I use the 'didTransition' action to toggle the class 'right'. This is called both then transitioning from 'list/index' to 'list/show', and when 'list/show' is the initial route. Unfortunately, if 'list/show' is the first route, none of the DOM elements exist when 'didTransition' is called.
I can envision two routes to a solution, but don't know how to implement either:
Toggle the class 'right' on some action which happens after DOM elements exist.
Insert conditional code in the 'list' template, which sets the class 'right' on 'panelList' and 'panelDetail' if the actual route is 'list/show'.
Suggestions?
Answer current as of Ember v2.12.0
You can use the link-to helper to render elements other than links, with styles that change based on the route. Utilizing the activeClass, current-when, and tagName properties, you can basically have that element be styled however you want depending on which route you are on. For example, to render your panelList div:
{{#link-to tagName='div' classNames='panelList' activeClass='right' current-when='list/show'}}
More markup
{{/link-to}}
I love a trick with empty component. In didInsertElement and willDestroyElement hooks you can add and remove a css class from parent element or (I like it better) body. Here is a code:
import Ember from 'ember';
export default Ember.Component.extend({
bodyClass: '',
didInsertElement() {
const bodyClass = this.get('bodyClass');
if (bodyClass) {
Ember.$('body').addClass(bodyClass);
}
},
willDestroyElement() {
const bodyClass = this.get('bodyClass');
if (bodyClass) {
Ember.$('body').removeClass(bodyClass);
}
}
});
I use it in template (in my example it's a template of player route) like this
{{body-class bodyClass='player-page-active'}}
To apply classes to parent element, you can use this.$().parent(), but using body is more reliable. Note that this component will create an empty div, but it shouldn't be a problem (can be in rare cases, fix it with classNames and css if needed).
Sukima suggested looking at currentRouteName, and I thus found hschillig's solution, which I simplified for my case. In the controller, I created an isShow function:
export default Ember.Controller.extend({
routing: Ember.inject.service('-routing'),
isShow: function() {
var currentRouteName = this.get('routing').get('currentRouteName');
return currentRouteName === 'list.show';
}.property('routing.currentRouteName'),
In the template, I now use the if helper:
<div class="panelList {{if isShow 'right'}}">
RustyToms's answer eliminates the need for adding a function to the Controller, at the expense of being less semantic.

Rendering a controller/template/route into another template

This is probably a very common task, but I am not sure which is the right approach to tackle this.
I have a Route / Controller / Model setup that loads groups. In the header area before the main content I want to show notifications for the user. I have created a notificationcontroller that is an arraycontroller and as well as a route that loads all notifications.
I don't want this route to be used for anything else than within this area.
I have already tried in my groups.hbs to add an additional {{ outlet notification }} and try to render in it:
App.GroupsRoute = GambifyApp.BaseRoute.extend({
model: function() {
return this.get('store').find('group');
},
renderTemplate: function() {
this.render('notifications', { // the template to render
// into: 'notifications', // the route to render into
outlet: 'notification', // the name of the outlet in the route's template
controller: 'notifications', // the controller to use for the template
});
this.render('groups');
}
});
But somehow my notifications template is not used. For now there is not much in my NotificationsController (It's an ArrayController) , NotificationsRoute is only reading all Notifications as Model and the template is currently only text, which is not rendered in my groups.hbs
The template containing the outlet you want to render into must be rendered first in order to render into the outlet. Here is an example of rendering into a named outlet that is in the application template from the index route:
http://emberjs.jsbin.com/qoyi/1/edit?html,js,output
Leave a comment if this example doesn't clear it up for you.

{{outlet}}, {{view}}, {{render}}, and {{control}} helpers

I am trying to put together a simple master-details Ember app. Directory tree on one side and file list on another.
Ember offers few helpers to render context into a view. Which of them I can use for:
Subtrees of the directory tree.
Details list.
In fact, would be very helpful if someone can point me to any docs I can read about the difference between {{render view}}, {{view view}} and {{control view}} helpers and how to use them properly.
Thanks a lot!
{{view "directory"}} renders the view within the context of the current controller.
{{render "directory"}} renders the view App.DirectoryView with template directory within the context of the singleton App.DirectoryController
{{control directory}} behaves the same way as render only it creates a new instance of App.DirectoryController every time it renders (unlike render which uses the same controller instance every time).
Update 18 Feb 2014: {{control}} has been removed.
The last two helpers are relatively new, so there isn't much documentation about them. You can find {{view}} documentation here.
Now looking at your use case, I don't think you need any of these helpers. Just use nested routes and the {{outlet}} helper and it should just work.
App.Router.map(function(){
this.resource('directories', function() {
this.resource('directory', { path: '/:directory_id'}, function() {
this.route('files');
});
});
});
You can build on that following this guide.
UPDATE: {{render}} now creates a new instance every time if you pass a model.
For a very good explanation of the helpers render, partial, outlet and template have a look at this question.
Just as a rough a summary, how one might use those helpers:
{{render "navigation"}} -> Renders the NavigationController and NavigationView at this place. This is helper is good for places, where the Controller and View do not change, e.g. a navigation.
{{outlet "detailsOutlet"}} -> This will provide a stub/hook/point into which you can render Components(Controller + View). One would use this with the render method of routes. In your case you will likely have a details route which could look like this. This would render the DetailsController with DetailsView into the outlet 'detailsOutlet' of the index template.
App.DetailsRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('details', { // the template/view to render -> results in App.DetailsView
into: 'index', // the template to render into -> where the outlet is defined
outlet: 'detailsOutlet', // the name of the outlet in that template -> see above
});
}
});
{{view App.DetailsView}} -> This will render the given view, while preserving the current context/controller. One might change the context, e.g. using your master entity and pass its details to a view like this:
{{view App.DetailsView contextBinding="masterEntity.details"}}
This is helper is useful, when you want to encapsulate certain parts of a component in subviews, that have own custom logic like handling of actions/events.
{{control}} I know that control instantiates a new controller every time it is used, but I cannot see a good fit for your, nor have i a good example for using it.
To Understand the difference between ember {{render}},{{template}},{{view}},{{control}}
you can refer this article
http://darthdeus.github.io/blog/2013/02/10/render-control-partial-view/

How can I bind the element ID for an Ember View?

My model "content.id" contains a string, e,g "123":
{{view Em.TextArea idBinding="content.id"}}
Instead of just setting the id of this view to "123", I'd like it to be "message-123", basically customizing the string being used. Sadly, Ember does not allow bindings to be functions, which would solve my problem (I could define such a function on the controller).
What's the best way to achieve this?
You could define a computed property in the controller (or elsewhere):
The controller
MyApp.ApplicationController = Ember.Controller.extend({
content: "a-content",
editedContent: function() {
return "message-" + this.get('content');
}.property('content')
});
The view
MyApp.FooView = Ember.View.extend({
    tagName: 'p'
});
The template (where content is a String, here)
{{#view MyApp.FooView elementIdBinding="editedContent"}}
{{content}}
{{/view}}
And the JSFiddle is here.
EDIT
How can the view see the property editedContent since it belongs on the ApplicationController controller?
The router, after started, automatically render the ApplicationView, or its template when there is no ApplicationView defined. If you want more detail, I suggest you to read the Ember guide: Understanding the Ember.js Router: A Primer.
And {{editedContent}} directly get the controller editedContent property, because the default view context is its controller, as you can read in Ember Blog - 1.0 Prerelease:
The {{#view}} helper no longer changes the context, instead maintaining the parent context by default. Alternatively, we will use the controller property if provided. You may also choose to directly override the context property. The order is as follows:
Specified controller
Supplied context (usually by Handlebars)
parentView's context (for a child of a ContainerView)