I'm trying to create something really simple with ember.js, and I'm getting badly lost between old examples, new examples, and extensive documentation.
I want to create an application with no functionality, that simply shows two sets of data in two columns. The real application uses JSON data from two different sources and is more complicated, but the problem reduces to getting the below to work:
<div id="appholder">
<script type="text/x-handlebars">
{{outlet left}}
{{outlet right}}
</script>
<script type="text/x-handlebars" data-template-name="left">
<div id="left">
<ul>
{{#each item in model}}
<li>{{item}}</li>
{{/each}}
</ul>
</div>
</script>
<script type="text/x-handlebars" data-template-name="right">
<div id="right">
<ul>
{{#each item in model}}
<li>{{item}}</li>
{{/each}}
</ul>
</div>
</script>
</div>
and in the javascript something like
App = Ember.Application.create({
rootElement: '#appholder'
});
App.LeftController = Ember.ArrayController.extend({
model: function() {
return ['left one', 'left two'];
}
});
App.RightController = Ember.ArrayController.extend({
model: function() {
return ['right one', 'right two'];
}
});
App.IndexRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('left', {
outlet: 'left',
controller: 'left',
});
this.render('right', {
outlet: 'right',
controller: 'right',
});
}
});
to output the hoped for result, a left column with list entries from one data source and a right column with two entries from another column.
Could somebody ideally provide a js fiddle with the above adapted to working code? Any part of it can change, to use {{render}} or {{view}} in the templates and whatever the js should be.
Thank you for any help
http://emberjs.jsbin.com/mifer/2/edit
Here is a working JSBin
First in order to use a function as a property, you must make it a computed property with the property() function:
App.LeftController = Ember.ArrayController.extend({
model: function() {
return ['left one', 'left two'];
}.property()
});
Secondly, the renderTemplate code is in the wrong place. You put it in the index route but in reality it should be in the application route. If you had {{outlet}} in your application template, the index template would have been rendered into it. Then, if you had those two named outlets inside the index template, what you had would have almost worked (you need to call this.render() or this._super() whenever you use renderTemplate if you want the route template to render.
But, you have two named outlets which you want to manually render into inside of your application template. Hence:
App.ApplicationRoute = Ember.Route.extend({
renderTemplate: function(){
this.render();
this.render('left', {outlet: 'left', into: 'application'});
this.render('right', {outlet: 'right', into: 'application'});
}
});
Now this next approach is how I tackle multiple models in my dashboard application. I am constantly needing to replace the sections of widgets so I use multiple named outlets. I've restructured your code so that the application template renders the index template into its single unnamed outlet.
Keys to this approach:
App.IndexRoute = Ember.Route.extend({
model: function(){
return Ember.RSVP.hash({
left: ['left one', 'left two'],
right: ['right one', 'right two']
});
},
renderTemplate: function(controller, model){
this.render();
this.render('left', {outlet: 'left', into: 'index', controller: 'left', model: model.left});
this.render('right', {outlet: 'right', into: 'index', controller: 'right', model: model.right});
}
});
Whenever you need to return multiple models, and you want your route to block until all models are returned, use Ember.RSVP.hash. You return multiple promises, each as properties of your returned model. RenderTemplate takes two parameters, controller and model so you access your model in the renderTemplate to manually pass the model into the controller of the template you are rendering.
As a slight alternative to this approach, if you need to render multiple datasources on the page, but you do not ever need to dynamically replace the whole template backing one of the models (ie render once and done), you can use the {{render}} helper.
<script type="text/x-handlebars" data-template-name="index">
<p>Index Template</p>
{{render 'left' model.left}}
{{render 'right' model.right}}
</script>
The benefit of this code is that our route has simplified.
App.IndexRoute = Ember.Route.extend({
model: function(){
return Ember.RSVP.hash({
left: ['left one', 'left two'],
right: ['right one', 'right two']
});
}
});
But, we have lost the ability to easily render something else here via action since we no longer have named outlets.
Related
I am trying to render a set of tabs for a set of objects (conversations) using the render helper for each. This is not part of a route as it is a persistent part of the interface. I have run into a problem where only the view with the same name as the model gets the intended controller (i.e. the panel contents and not the tab headers).
I have a Chat model, object controller and array controller (deliberately simplified here):
App.Chat = DS.Model.extend({ });
App.ChatsController = Ember.ArrayController.extend({
needs: 'application',
content: Ember.computed.alias('controllers.application.currentChats'),
});
App.ChatController = Ember.ObjectController.extend({ });
The ArrayController needed the needs/content properties because the chats are loaded in the application controller. I used the currentChats name as other routes may load non-current chats.
App.ApplicationController = Ember.Controller.extend({
init: function(){
this.store.find('chat', {"current": true});
this.set('currentChats', this.store.all('chat'));
}
});
I have no difficulty rendering the chat contents with the appropriate controller (into the 'chat' template). However, the chat tabs are given the default ObjectController, and therefore can't fire actions.
<script type="text/x-handlebars" id="application">
<!--application template-->
{{outlet chats}}
</script>
<script type="text/x-handlebars" id="chats">
<div id="chats">
<ul id="chat-tabs">
{{#each}}
{{render 'chatTab' this}}
{{/each}}
</ul>
{{#each}}
{{render 'chat' this}}
{{/each}}
</div>
</script>
<script type="text/x-handlebars" id="chatTab">
<!--tab template-->
</script>
<script type="text/x-handlebars" id="chat">
<!--chat template-->
</script>
The application router is as follows:
App.ApplicationRoute = Ember.Route.extend({
model: function(){ },
renderTemplate: function(){
this.render('application', { });
this.render('chats', {
into: 'application',
outlet: 'chats',
controller: 'chats'
});
}
});
This seems to come solely down to naming of the templates. The template called 'chat' inherits the correct controller, but chatTab doesn't despite receiving a chat as the model. Is there any way to force the view to inherit the correct controller? Or am I going about this in an idiosyncratic way.
Many thanks for your help to this Ember novice.
Andrew
It goes solely off the name provided to the render. The easiest way is to just create the other controller and extend the chat controller.
App.ChatTabController = App.ChatController.extend();
I am going to bind model to template with emberjs
<script type="text/x-handlebars" id="dashboard">
<div>
<span>this is user list</span>
<div>
{{render userinfo userinfo}}
</div>
</div>
</script>
<script type="text/x-handlebars" id="_userinfo">
{{#each model}}
<span>{{user}}
{{/each}}
</script>
App.Userinfo= DS.Model.extend({
user: DS.attr("string")
});
App.Userinfo.FIXTURES = [
{user:"user1"},
{user:"user2"},
{user:"user3"}
];
App.UserinfoView= Ember.View.extend({
});
App.UserinfoController = Ember.ObjectController.extend({
});
App.Router.map(function() {
this.resource('dashboard', {path: '/dashboard'}, function() {
});
});
App.DashboardRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('dashboard', { // the template to render
controller: 'dashboard' // the controller to use for the template
});
}
});
App.DashboardController = Ember.ObjectController.extend({
});
When i go to /#/dashboard, Dashboard template is loaded.
In here, I have rendered userinfo.
I'd like to bind Userinfo Model to usersinfo template so that I display all users.
Help me, please.
The short: here a working jsbin.
The long: You hade slightly to much unnecessary going on in your code, basically this does the job:
First of all you had no redirect to your dashboard route, since it's your only route (at least as far I can see from your code) we redirect directly to it after entering the index route
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('dashboard');
}
});
I've removed the DashboardController since there is nothing to be done.
Then your DashboardRoute was missing the model hook to provide actually data for your dashboard template
App.DashboardRoute = Ember.Route.extend({
model: function(){
return App.Userinfo.find();
}
});
In the router map you don't need to define a path if the URL is the same name as your template name in your case it is the same dashboard
App.Router.map(function() {
this.resource('dashboard');
});
The userinfo model was correct
App.Userinfo= DS.Model.extend({
user: DS.attr("string")
});
But your fixtures where missing the id
App.Userinfo.FIXTURES = [
{id:1, user:"user1"},
{id:2, user:"user2"},
{id:3, user:"user3"}
];
Moreover you where using the render helper with a partial template _userinfo the correct way to render a partial is this
{{partial userinfo}}
As you can see we don't pass any additional parameters to it because the data will be made available trough your model hook. The partial helper uses the context and the data provided in the template it is rendered into, in your case the dashboard template, therefore the model hook is necessary.
Hope it helps.
I'm trying to render a modal. For that I've created a custom outlet using {{outlet modalOutlet}} My application template has two outlets, the default outlet and the modalOutlet. However when the modal template is rendered into {{outlet modalOutlet}}, my default {{outlet}} becomes empty.
How do I change it, so that the default {{outlet}} doesn't change, so I can actually render {{outlet modalOutlet}} as modal window, or as a sidebar as a part of a layout
I'm not sure if this is due to my code, or something about the renderTemplate() method that I'm missing. The jsFiddle with my code is here.
// Router
App.Router.map(function(){
this.route('contributors');
this.route('contributor', {path: '/contributors/:contributor_id'});
});
App.ContributorsRoute = Ember.Route.extend({
model: function() {
return App.Contributor.all();
},
});
App.ContributorRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('contributor', {
outlet: 'modalOutlet'
});
}
});
<script type="text/x-handlebars" data-template-name="application">
<nav>
{{#linkTo "index"}}Home{{/linkTo}}
{{#linkTo "contributors"}}Contributors{{/linkTo}}
</nav>
<div style='padding:5px;margin:5px;border:1px dotted red;'>
Default Outlet
{{outlet}}
</div>
<div style='padding:5px;margin:5px;border:1px dotted blue;'>
modalOutlet
{{outlet modalOutlet}}
</div>
</script>
You must render the contributors template as well, since the default outlet gets cleared when you transition to a sibling route.
App.ContributorRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('contributors');
this.render('contributor', {
outlet: 'modalOutlet'
});
}
});
You can avoid this, however, if you nest your routes like this:
App.Router.map(function(){
this.resource('contributors', function() {
this.route('show', {path: ':contributor_id'});
});
});
...and adjust the rest of your app to match the new structure. In this case, you need to specify the place the modalOutlet lies with the into option (in this case: 'application')
The issue is your routing structure is not nested, and once you nest your routes you will need to specify the route which contains the modal outlet.
What is happening is you render
Application -> Contributors
to show your list, but when you click a link you are now rendering
Application -> Contributor
and the Contributor template is removed.
If you nest your routes, like this:
Application -> Contributors -> Contributor
Then you will still have the Contributors template showing the list.
updated JSFiddle
//Router
App.Router.map(function(){
this.resource('contributors', function() {
this.resource('contributor', {path: '/:contributor_id'});
});
});
//Route
App.ContributorRoute = Ember.Route.extend({
renderTemplate: function() {
this.render('contributor', {
into: 'application',
outlet: 'modalOutlet'
});
}
});
I have a list of matches, and when I click one, I want to display the match. I know that I can do a Master-Detail style page, where when I click one, I can see the outlet somewhere on the same page, but that is not what I want.
I want it so that when I click on a link, it goes to an entirely new page for the match. I'm not really sure how to go about doing that.
Here is my route for #/matches (in coffeescript)
App.MatchesRoute = Ember.Route.extend(
model: ->
App.Match.find()
)
Here is my matches.handlebars
<div id="matches">
{{#each match in controller}}
{{#linkTo "match" match class="panel six columns"}}
Match between {{match.player.name}} and {{match.opponent.name}}
{{/linkTo}}
<br />
{{/each}}
</div>
// I know that if I have this outlet, it will render `match.handlebars`
// right here, but I want it to be it's own page.
// {{outlet}}
I've only been working with Ember for a few days, and all of the examples I've found use Master-Detail views.
Please let me know of any other information I can provide from my code.
<Edit date="March 11th 2013">
I've pushed a this repository in GitHub. This is a conceptual app that uses renderTemplate somewhat the way you're describing.
</Edit>
In your child route, use the renderTemplate hook in order to tell your application to render a specific template in a specific {{outlet}}. Example:
Source Fiddle
App.Router.map(function() {
this.resource('matches', { path: 'matches' }, function() {
this.route('match', { path: 'match/:match_id' });
});
});
App.MatchesRoute = Em.Route.extend({
model: function() {
return App.Match.find();
},
setupController: function(controller, model) {
model = App.Match.find();
controller.set('content', model);
},
renderTemplate: function() {
this.render('matches', {
into: 'application'
})
}
});
App.MatchesMatchRoute = Em.Route.extend({
model: function(params) {
return App.Match.find(params.match_id);
},
setupController: function(controller, model) {
controller.set('content', model);
},
renderTemplate: function() {
this.render('match', {
into: 'application'
})
}
});
This MatchesMatchRoute is setup to render its template (matches/match) into the application template. And since there is only one {{outelet}} this template (see handlebars below), we don't have to specify anything:
<script type="text/x-handlebars">
<h1>App</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="matches">
<h2>Matches</h2>
<ul>
{{#each match in controller}}
<li>
{{#linkTo matches.match match}}
{{match.title}}
{{/linkTo}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="match">
<h3>{{title}}</h3>
<p>{{description}}</p>
</script>
If you have a scenario with multiple outlets, you have to hame them, like in the handlebars below:
<script type="text/x-handlebars">
<h1>App</h1>
{{outlet main}}<br />
{{outlet nested}}
</script>
Then your routes will have to specify the outlet as well. Example:
Source Fiddle
[...route code...]
renderTemplate: function() {
this.render('content', {
into: 'application',
outlet: 'main'
});
this.render('buttons', {
into: 'application',
outlet: 'nested'
});
}
[...route code...]
You can cause a template to render into a different template's outlet by using the renderTemplate hook when defining the route (see the guide: http://emberjs.com/guides/routing/rendering-a-template/)
For your example it might look like this:
App.MatchRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({ into: 'matches' });
}
});
UPDATE Since asking this question I have redesigned my UI such that I no longer need this feature; however I'm leaving this open and active for the sake of helping others who end up with a similar problem.
I'm listing a collection of elements inside a template and each element has a link that opens it up to the right of the list. When one is clicked, I want to hide just that element and show it again when another one is clicked. My current approach to doing this is to set an attribute (active) to true on the model. This feels wrong for three reasons:
This attribute is not actually part of the model's schema, it's just arbitrary; which makes it seem like a controller concern (see below for why that doesn't work)
I have to first set active to false on all models, forcing me to change another router's model, which may be good or bad, I'm not sure
In the recent PeepCode screencast he showed using #each.{attribute} to bind to an attributes in an array; this makes me feel like there must be something similar I could do (like this.set("#each.active", false)) to set them all in one fell swoop
I wanted to use a method on the controller but it doesn't seem I can pass arguments into functions in Handlebars if statements.
Here's the code I'm using to render the list:
{{#each note in controller}}
{{!v-- I was trying to do {{#if isCurrentNote note}} but that seems to be invalid handlebars}}
{{#unless note.active}}
<li class="sticky-list-item">
{{view Ember.TextArea classNames="sticky-note" valueBinding="note.content"}}
{{#linkTo note note classNames="sticky-permalink"}}
∞
{{/linkTo}}
</li>
{{/unless}}
{{/each}}
And here are the routes:
App.NotesController = Ember.ArrayController.extend({
// v-- this is what I was trying to do, but couldn't pass note in the template
isCurrentNote: function(note){
return this.get("currentNote") == note;
}.property("currentNote")
});
App.NoteRoute = Ember.Route.extend({
setupController: function(controller,model){
this.modelFor("notes").forEach(function(note){
note.set("active", false);
});
model.set("active", true);
}
});
Like I said, what I have works, but it feels wrong. Can anyone confirm my suspicion or help ease my soul a bit?
Thanks!
to me this looks like something that should be done mostly by the NotesView with a NotesController that stores the Note selection
Here is the fiddle: http://jsfiddle.net/colymba/UMkUL/6/
the NotesController would hold all the notes and a record of the selected one:
App.NotesController = Ember.ArrayController.extend({
content: [],
selectedNote: null,
selectNote: function(id){
var note = this.get('content').findProperty('id', id);
this.set('selectedNote', note);
}
});
with the NotesViewobserving that selection and showing/hiding elements of the list accordingly
App.NotesView = Ember.View.extend({
templateName: 'notes',
refresh: function(){
var view = this.$(),
selection = this.get('controller.selectedNote');
if (view) {
view.find('li').show();
if (selection) view.find('li.note_'+selection.id).hide();
}
}.observes('controller.selectedNote')
});
Here is the Note object and it's 2 templates (when in a list or displayed in full). The ListView handles the click event and passes the id to the NotesController.
App.Note = Ember.Object.extend({
name: '',
content: ''
});
App.NoteView = Ember.View.extend({
templateName: 'note'
});
App.NoteListItemView = Ember.View.extend({
tagName: 'li',
templateName: 'noteListItem',
classNameBindings: ['noteID'],
noteID: function(){
return 'note_' + this._context.id;
}.property(),
click: function(e){
this.get('controller').selectNote(this._context.id);
}
});
in the NotesView template everything is displayed and if there is a selectedNote, we display the Note again in full:
{{#each note in controller}}
{{#with note}}
{{view App.NoteListItemView}}
{{/with}}
{{/each}}
{{#if selectedNote}}
{{#with selectedNote}}
{{view App.NoteView}}
{{/with}}
{{/if}}
the Routes to put it together
App.Router.map(function() {
this.resource('notes', { path: "/notes" });
});
App.IndexRoute = Ember.Route.extend({
enter: function() {
this.transitionTo('notes');
}
});
App.NotesRoute = Ember.Route.extend({
model: function() {
return [
App.Note.create({id: 1, name: 'Milk', content: '15% fresh milk'}),
App.Note.create({id: 2, name: 'Juice', content: 'Orange with pulp'}),
App.Note.create({id: 3, name: 'Cereals', content: 'Kelloggs Froot Loops'}),
];
},
setupController: function(controller, model) {
controller.set('content', model);
},
renderTemplate: function(controller, model) {
this.render('notes', { outlet: 'content' });
}
});