See the jsbin example here
Here is my code:
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
}
});
App.DerpMenuComponent = Ember.Component.extend({
items: null,
createSteps: function() {
this.set('items', Ember.ArrayProxy.create({content: []}));
}.on('init'),
register: function(item) {
this.get('items').addObject(item);
}
});
App.DerpMenuItemComponent = Ember.Component.extend({
title: null,
register: function() {
this.get('parentView').register(this);
}.on('didInsertElement')
});
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>
<script src="http://builds.emberjs.com/tags/v1.8.0/ember.js"></script>
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{outlet}}
</script>
<script type="text/x-handlebars" id="index">
{{#derp-menu}}
{{#each item in model}}
{{#derp-menu-item title=item}}
<div style="background: #CCC; color: #00F; margin: 10px; padding: 5px">{{item}}</div>
{{/derp-menu-item}}
{{/each}}
{{/derp-menu}}
</script>
<script type="text/x-handlebars" id="components/derp-menu">
<ul>
{{#each item in items}}
<li>{{item.title}}</li>
{{/each}}
{{yield}}
</ul>
</script>
<script type="text/x-handlebars" id="components/derp-menu-item">
{{yield}}
</script>
I can see, that the derp-menu-elements register in reverse order, when adding them into the template dinamically, using the #each helper. Can anyone please explain why that is the case? If I manually type out the derp-menu-items inside the derp-menu component - everything is displayed correctly, I'm guessing that's because Handlebars somehow act differently in that scenario. Can anyone provide insights into what's happening in both of these cases?
the didInsertElement event always fire from the last/lowest level and then moves up the tree. This is so that when didInsertElement is fired on a parent element you can be sure that all the children are already inserted in the dom.
In your case you are registering with the parent .on('didInsertElement') you are ending up with the elements getting added to the array in the reverse order.
If you changed your code to be based on .on('init') (like below) then they would get be in the same order.
App.DerpMenuItemComponent = Ember.Component.extend({
title: null,
register: function() {
this.get('parentView').register(this);
}.on('init')
});
You can see a working example here: http://emberjs.jsbin.com/hirolu/1/edit?html,js,output
Related
I have a route in an Ember app called "basic" corresponding to the name of an API endpoint.
This route doesn't work - links to it don't render its template.
Here's a JSBin demonstrating the failure: http://emberjs.jsbin.com/hisoxadi/1
JS:
App = Ember.Application.create();
App.Router.map(function() {
this.route('basic');
this.route('test');
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
}
});
Templates:
<script type="text/x-handlebars">
<h2> Welcome to Ember.js</h2>
{{#link-to 'index'}}Index{{/link-to}}
{{#link-to 'basic'}}Basic Route{{/link-to}}
{{#link-to 'test'}}Test Route{{/link-to}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="basic">
basic route is here
</script>
<script type="text/x-handlebars" data-template-name="test">
test route is here
</script>
<script type="text/x-handlebars" data-template-name="index">
<ul>
{{#each item in model}}
<li>{{item}}</li>
{{/each}}
</ul>
</script>
To expand on your comment a bit, basic is indeed a reserved word. Specifically, it's a reserved word for the resolver. You can see the source here.
useRouterNaming: function(parsedName) {
parsedName.name = parsedName.name.replace(/\./g, '_');
if (parsedName.name === 'basic') {
parsedName.name = '';
}
},
And because of the way that Ember.js sometimes looks up routes and controllers in the container, it's a fair bet to say that there's no way around this without major code changes. There should probably be a documentation issue filed for this.
EDIT: I created an issue for this here.
I think this should be a really easy fix, i just cant figure it out.
I have this model in my ApplicationRoute:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return {
sideNav: data.side,
breadcrumbs: Util.breadcrumbs()
}
}
});
application template:
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
overview template:
<script type="text/x-handlebars" data-template-name="overview">
{{partial side}}
</script>
_side partial:
<script type="text/x-handlebars" id="_side">
{{#each model.side}}
<i {{bindAttr class="iconClass"}}></i><p>{{label}}</p>
{{/each}}
</script>
When I load my overview route, the stuff in there loads, but the application model is not applied to the side partial like I would like it to be... there may be a better way to do this. Thanks!
It looks like you have two problems:
You are binding to side instead of sideNav inside your partial.
You're trying to access the model of ApplicationRoute inside of the overview template (which presumably has its own route and controller)
Partials don't change context, so they don't add any complexity on their own. For instance, if we restrict ourselves to the application template, the following will work:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return {
foo: 'bar'
};
}
});
<script type="text/x-handlebars" data-template-name="application">
{{partial partial}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="_partial">
{{foo}}
</script>
However, if you want to get at the application model inside of a nested route, you'll need to ask for it. For example, inside of the (default) index route, you could add the following:
App.IndexRoute = Ember.Route.extend({
model: function() {
return this.modelFor('application');
}
});
<script type="text/x-handlebars" data-template-name="index">
{{partial partial}}
</script>
You can see both examples in this jsfiddle.
In my usecase, I want to navigate from parent route to a child route and from child to parent. The first case work, but the second not. Why? What is wrong? Is the mistake in renderTemplate?
App = Ember.Application.create();
App.Router.map(function() {
this.resource('parent',function(){
this.resource('child',function(){
});
});
});
App.ChildRoute = Ember.Route.extend({
renderTemplate: function(){
this.render('child', {
into: 'application',
controller: 'child'
});
}
});
<script type="text/x-handlebars">
<h1>Ember Sandbox</h1>
<nav>
{{#linkTo 'parent'}} Parent {{/linkTo}}
</nav>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Welcome</h2>
</script>
<script type="text/x-handlebars" data-template-name="parent">
<h2>Parent</h2>
{{#linkTo 'child'}} Child {{/linkTo}}
</script>
<script type="text/x-handlebars" data-template-name="child">
<h2>Child</h2>
{{#linkTo 'parent'}} Parent {{/linkTo}}
</script>
Whenever you nest a resource, emberjs provides an Index route (App.ParentIndexRoute). When you transition from child resource to parent, the parent template is already rendered and hence it will be redirected to the index route (App.ParentIndexRoute).
Rendering your parent template in App.ParentIndexRoute will solve your problem
App.ParentIndexRoute = Ember.Route.extend({
renderTemplate: function(){
this.render('parent', {into: 'application'});
}
});
Your Working jsbin
Somehow I don't understand how to implement views which require a certain controller with ember.js. I'll try to explain the issue in which I run with an example:
Let's say I would like to build a view component which creates a simple todo list: It contains a textfield. Underneath of that field is a list of all todo items that have been entered so far. That view should have a controller which stores the todo items (since I don't have to push to store them persistent at that point).
I'd like the view to be usable in different places of the webapp (through my handlebars templates), so it should be independent of the route through which it is accessed.
My templates look like this:
<?xml version="1.0" encoding="utf-8"?>
<html>
<head>
<script src="../shared/libs/jquery-1.9.1.js"></script>
<script src="../shared/libs/handlebars-1.0.0-rc.3.js"></script>
<script src="../shared/libs/ember-1.0.0-rc.1.js"></script>
<script src="todo.js"></script>
</head>
<body>
<script type="text/x-handlebars" data-template-name="application">
{{ view App.TodolistView }}
</script>
<script type="text/x-handlebars" data-template-name="todolistView">
{{view App.CreateTodoTextField
placeholder="Enter your todo here"
size=100
}}
<ul>
{{#each todo in controller}}
<li>{{todo}}</li>
{{/each}}
</ul>
</script>
</body>
</html>
My App Logic looks like this:
window.App = Ember.Application.create();
App.TodolistView = Ember.View.extend({
templateName: 'todolistView'
});
App.TodolistController = Ember.ArrayController.create({
content: [],
createItem: function(item) {
this.pushObject(item);
}
});
App.CreateTodoTextField = Ember.TextField.extend({
insertNewline: function() {
var value = this.get('value');
if (value) {
App.TodolistController.createItem(value);
this.set('value', '');
}
}
});
I have two problems with that code:
When my TodolistView tries to access the controller with
{{#each todo in controller}}
it actually accesses the ApplicationController, not the TodolistController
Also calling
App.TodolistController.createItem(value);
looks like a bad idea to me: I.e. what if I would have two TodoListViews on one page? createItem should be rather called on the current instance of the TodolistController...
Probably I'm missing a core concept for defining views with ember.js...
App.CreateTodoTextField = Ember.TextField.extend({
insertNewline: function() {
var value = this.get('value');
if (value) {
App.TodolistController.createItem(value);
this.set('value', '');
}
}
});
Todoliscontroller can't be accessed like this. Only its instance can be accessed in the below way:
var controller = this.get('controller');
controller.controllerFor('todolist').createItem(value);
Thanks for the hints, this is what, works for me, but I'm still not sure if this is the way its intended to be used:
HBS
<script type="text/x-handlebars" data-template-name="todolistView">
{{view App.CreateTodoTextField
placeholder="Enter your todo here"
size=100}}
<ul>
{{#each todo in controllers.todolist}}
<li>{{todo}}</li>
{{/each}}
</ul>
</script>
App.ApplicationController = Ember.Controller.extend({
needs: ['todolist']
});
JS
App.CreateTodoTextField = Ember.TextField.extend({
insertNewline: function() {
var value = this.get('value');
if (value) {
this.get('controller').get('controllers.todolist').createItem(value);
this.set('value', '');
}
}
});
I tried this very basic ember router example following the ember-router-example. But when I run it, it shows me an empty page. I checked the console window for any errors, but seems to be fine. Not really sure why this is not working and where am missing.
I am just trying to create the first level links of Home, Sections, items only.
Can somebody help me?
index.html:
<body>
<script src="js/libs/jquery-1.7.1.js"></script>
<script src="js/libs/jquery.lorem.js"></script>
<script src="js/libs/bootstrap.min.js"></script>
<script src="js/libs/handlebars-1.0.0.beta.6.js"></script>
<script src="js/libs/ember.js"></script>
<script src="js/app.js"></script>
<script type="text/x-handlebars" data-template-name="application">
<div>
<ul>
<li><a {{action "doHome"}}>Home</a></li>
<li><a {{action "doSections"}}>Sections</a></li>
<li><a {{action "doItems"}}>Items</a></li>
</ul>
</div>
<div>
{{outlets}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="home">
<h1>yeah right Home</h1>
</script>
<script type="text/x-handlebars" data-template-name="sections">
<h1>Oh v in Sections index</h1>
</script>
<script type="text/x-handlebars" data-template-name="items">
<h1>correct in Items Index Page</h1>
</script>
</body>
app.js :
$(function() {
App = Ember.Application.create()
App.ApplicationController = Ember.Controller.extend();
App.ApplicationView = Ember.View.extend({
templateName:'application'
});
App.HomeController = Ember.Controller.extend();
App.HomeView = Ember.View.extend({
templateName:'home'
});
App.SectionsController = Ember.Controller.extend();
App.SectionsView = Ember.View.extend({
templateName:'sections'
});
App.ItemsController = Ember.Controller.extend();
App.ItemsView = Ember.View.extend({
templateName:'items'
});
App.Route = Ember.Route.extend({
root: Ember.Route.extend({
doHome : function(router,event){
router.transitionTo('home');
},
doSections:function(router,event){
router.transitionTo('sections');
},
doitems:function(router,event){
router.transitionTo('items');
},
home : Ember.Route.extend({
route : '/',
connectOutlets:function(router,event){
router.get('applicationController').connectOutlet('home');
}
}),
sections : Ember.Route.extend({
route : '/sections',
connectOutlets:function(router,event){
router.get('applicationController').connectOutlet('sections');
}
}),
items : Ember.Route.extend({
route : '/items',
connectOutlets:function(router,event){
router.get('applicationController').connectOutlet('items');
}
})
})//root
}) //router
});
I created this fiddle with your code. It seems to be working, just use latest ember and handlebars. And maybe you should change {{outlets}} with {{outlet}}.
EDIT
The above fiddle is not working, see the updated fiddle.
I rewrote the routing code using the new routing API, now it is working as expected.
I believe you are supposed to be using "template" as opposed to templateName when you are defining your templates in your main html file. If you were to create those templates as separate handlebars files and use a build step, you would then use templateName to refer to them (by file name).
Steve