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', '');
}
}
});
Related
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
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'm creating an Ember application to display twitter feeds but I am having trouble with displaying individual tweets through embedded resources.
The code is as follows:
Templates
<script type="text/x-handlebars" data-template-name="tweets">
<div id="stream">
{{#each tweet in controller}}
<div class="tweet">
<p class="tweet_text">{{tweet.text}}</p>
<p> {{#linkTo "tweet" tweet}} {{tweet.id}} {{/linkTo}}</p>
</div>
{{/each}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="tweet">
<div id="detail">
{{text}}
</div>
</script>
Router
window.App = Ember.Application.create({
LOG_TRANSITIONS: true
});
App.Router.map(function(){
this.resource('tweets',function(){
this.resource('tweet',{path: ':tweet_id'})
});
});
// (1) App.Router.map(function(){
// this.resource('tweets')
// this.resource('tweet',{path: ':tweet_id'})
// });
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('tweets');
}
});
App.TweetsRoute = Ember.Route.extend({
model: function(){
var me = [];
$.getJSON("http://search.twitter.com/search.json?q=emberjs&rpp=200&count=200&callback=?",
{},
function (data) {
$.each(data.results,function(k,tweet){
var tweet = App.Tweet.create({
created_at: tweet.created_at,
from_user: tweet.from_user,
profile_image_url: tweet.profile_image_url,
text: tweet.text,
id: tweet.id
});
me.pushObject( tweet );
});
});
return me;
}
});
Objects & Controllers
App.TweetsController = Ember.ArrayController.extend({});
App.Tweet = Ember.Object.extend({
created_at: "",
from_user: "",
profile_image_url: "",
text: "",
id: 0
})
As you can see, I have a commented our router (1) which works in finding the correct tweet, and rendering it in the tweet template. However, I would like this route to be nested so that I can implement it as a Master-Detail application.
Using the LOG_TRANSITIONS, I can see that the correct routes are initialised, but I cannot get the nested resource path to render.
Any ideas would be hugely appreciated, thanks in advance.
I got this working. For anyone stuck on something similar, this is how I did it:
Templates - Changed the {{#linkTo}} "tweet"... to {{#linkTo}} "tweets.tweet"... AND added an {{outlet}}
<script type="text/x-handlebars" data-template-name="tweets">
<div id="stream">
{{#each tweet in controller}}
<div class="tweet">
<p class="tweet_text">{{tweet.text}}</p>
<p> {{#linkTo "tweets.tweet" tweet}} {{tweet.id}} {{/linkTo}}</p>
</div>
{{/each}}
</div>
{{ outlet }}
</script>
Router - Changed 'this.resource' to 'this.route'
App.Router.map(function(){
this.resource('tweets',function(){
this.route('tweet',{path: ':tweet_id'})
});
});
Caveat
I think this is a workaround and that the nested resource was the correct approach in this context. I understand that a nested route should be "a verb" or action route. I would still be grateful if anyone knows the correct approach to the question but hope the above helps others where relevant.
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
There is a strange issue am facing with ember.js, i.e when I add "data-template-name" attribute to the script tag, nothing is working. If data-template-name name is removed, things are working fine.
This is NOT WORKING
<script data-template-name="my-template" type="text/x-handlebars">
Input:
{{#view App.MyView}}
{{view Ember.TextField
valueBinding="view.theValue"
placeholder="input ..." }}
{{/view}}
</script>
Now if **data-template-name="my-template"** is removed it works fine.
I mean, In UI TextField is visible.
<script type="text/x-handlebars">
Input:
{{#view App.MyView}}
{{view Ember.TextField
valueBinding="view.theValue"
placeholder="input ..." }}
{{/view}}
</script>
App.MyView = Ember.View.extend({
templateName: 'my-template',
theValue: null,
init: function(){
this._super();
this.set('theValue','asdf');
},
keyDown: function(e){
if(e.keyCode === 13){
alert(this.get('theValue'));
}
}
});
Ok, don't worry about the jsfiddle, it's a good start :). There is a link on the ember.js site, where you can find a starting point, including Ember.js related sources: http://emberjs.com/community/
That beeing said, when creating an Ember.js application like this, there is an implicit default anonymous template, in wich you can then define yours.
<!-- default template, introducing the view App.MyView -->
<script type="text/x-handlebars">
{{view App.MyView}}
</script>
<!-- template of App.View -->
<script data-template-name="my-template" type="text/x-handlebars">
Input:
{{view Ember.TextField
valueBinding="view.theValue"
placeholder="input ..."}}
</script>
javacript:
App.MyView = Ember.View.extend({
templateName: 'my-template',
theValue: null,
init: function(){
this._super();
this.set('theValue','asdf');
}
});
Here is a working example of your sample:
http://jsfiddle.net/6p6XJ/171/