itemControllers and custom Views - ember.js

I am working on a small app that animates different iframes in and out of view. Right now I am just trying to start simple with two iframes for my data.
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
model: function() {
return [
{current: true, url:'http://www.flickr.com'},
{url:'http://bing.com'}
];
}
});
App.IndexController = Ember.ArrayController.extend({
itemController: 'iframe',
now: function() {
return this.filterBy('isCurrent').get('firstObject');
}.property('#each.isCurrent')
});
App.IframeController = Ember.ObjectController.extend({
isCurrent: Ember.computed.alias('current')
});
App.IframeView = Ember.View.extend({
classNameBindings: [':slide', 'isCurrent'],
templateName: 'iframe'
});
And my templates:
<script type="text/x-handlebars" data-template-name="index">
<button {{action "next"}}>Next</button>
{{#each}}
{{view "iframe"}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="iframe">
<iframe {{bind-attr src=url}}></iframe>
</script>
Why can't my IframeView access my isCurrent property of my itemController? I am also unsure if this is the right way to do this, or if there is an easier way to have my each use my IframeView
Here is a jsbin: http://emberjs.jsbin.com/vagewavu/4/edit

isCurrent lives on the controller. The controller property will be in scope in the view, but the properties under the controller aren't in scope of the view. You just need to reference controller first.
App.IframeView = Ember.View.extend({
classNameBindings: [':slide', 'controller.isCurrent'],
templateName: 'iframe'
});
Additionally your next action isn't doing anything, just creating some local variables, maybe you weren't finished implementing it. Either way I tossed together an implementation.
next: function() {
var now = this.get('now'),
nowIdx = this.indexOf(now),
nextIdx = (nowIdx + 1) % this.get('length'),
next = this.objectAt(nextIdx);
now.toggleProperty('current');
next.toggleProperty('current');
}
http://emberjs.jsbin.com/vagewavu/10/edit

Related

Component layout not rendered in ContainerView

I'm having trouble adding a Component dynamically to a ContainerView. See this jsfiddle:
http://jsfiddle.net/theazureshadow/7n2hz/
The component root element is placed in the container, but the layout associated with the component is not rendered.
App = Ember.Application.create({});
App.IndexRoute = Ember.Route.extend({});
App.IndexView = Ember.View.extend({
didInsertElement: function() {
console.log('IndexView inserted');
var container = this.get('container');
container.pushObject(Ember.TextField.create());
container.pushObject(App.MyComponent.create());
}
});
App.MyContainer = Ember.ContainerView.extend({
didInsertElement: function() {
console.log('MyContainer inserted');
}
});
App.MyComponent = Ember.Component.extend({
classNames: ['my-component'],
//template: Ember.Handlebars.compile('<p>Compiled directly</p>'),
didInsertElement: function() {
console.log('MyComponent inserted');
}
});
If I uncomment the template property, it does render that content properly. I also insert the component directly into the index view, where it renders properly (though the classNames are not added in this case).
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Render component directly:</h2>
{{my-component}}
<hr/>
<h2>Add component to container:</h2>
{{view App.MyContainer viewName="container"}}
</script>
<script type="text/x-handlebars" data-template-name="components/my-component">
<b>My component</b>
</script>
I've tried messing around with Ember.run, but no luck so far. Is didInsertElement the wrong hook for pushObject?
That should fix your problem. See the layoutName property.
App.MyComponent = Ember.Component.extend({
classNames: ['my-component'],
layoutName: 'components/my-component',
didInsertElement: function() {
console.log('MyComponent inserted');
}
});

Ember.js persist classNameBindings on transition to different routes

I'm fairly new to ember and I've been trying to tackle this problem for a couple of days but I can't seem to find a solution anywhere online.
I have a page with a list of all posts, each post has one tag (like a hashtag), either 'Fitness', 'Knowledge' or 'Social'. At the top of the page I have 3 view helpers and each view helper represents a tag (fitness, knowledge or social). These will be used to filter out the posts with that particular tag name.
My problem is that when I click on a view helper I toggle the "isSelected" property to true, which adds the "isSelected" class via classNameBindings. But when I transition to a different route on the site and come back, the "isSelected" property is reset back to false and the "isSelected" class has been removed. How do I keep these values persistent and in-tact for when I revisit the route?
Here's my code:
<script type="text/x-handlebars" data-template-name="global">
<ul class="categories">
<li>{{view App.Tag class="label fitness" text="fitness"}}</li>
<li>{{view App.Tag class="label knowledge" text="knowledge"}}</li>
<li>{{view App.Tag class="label social" text="social"}}</li>
</ul>
</script>
View:
"use strict";
App.Tag = Ember.View.extend({
tagName: 'span',
template: Ember.Handlebars.compile('{{view.text}}'),
classNames: ['label'],
classNameBindings: ['isSelected'],
isSelected: false,
click: function () {
this.toggleProperty('isSelected');
}
});
I have also tried using a controller with actions but that way persisted the "isSelected" property but didn't preserve the addition of the class when I revisited the route.
This may not be ideal, but to save the state of the application, you can put the state in the controller. You probably had a simple implementation, but maybe did not specify the isSelected as a property. The below works and you can view the jsbin here
App = Ember.Application.create();
App.Router.map(function() {
this.route('global');
});
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
}
});
App.GlobalController = Ember.Controller.extend({
activeTags: Ember.A()
})
App.Tag = Ember.View.extend({
tagName: 'span',
template: Ember.Handlebars.compile('{{view.text}}'),
classNames: ['label'],
classNameBindings: ['isSelected'],
isSelected: function () {
console.log("ON CHANGE", this.get('controller.activeTags'));
return this.get('controller.activeTags').contains(this.text);
}.property('controller.activeTags.#each'),
click: function () {
var tagArray = this.get('controller.activeTags');
if (tagArray.contains(this.text))
this.set('controller.activeTags', tagArray.without(this.text))
else
tagArray.pushObject(this.text);
}
});

Ember does not render after transition

I have just written extremly simple Ember app, built on top of the Rails app, working with Ember Data and displaying, creating and persisting just one entity type to the server. Everything with the latest tools (Ember v1.0.0-pre.4-134-gaafb5eb).
However, there is very strange problem I have encountered. My app has two views: entity list (index) and form for creating new entities. When I enter the index directly, everything displays OK. But when I go to the other view and then back to the list, the view is not rendered again. Where could be the problem?
I guess it might be caused by my (maybe incorrect) using new Ember router. So I'm pasting important (from my point of view) parts of the app here:
Router:
App.Router.map(function() {
this.resource('bands', function() {
this.route('new');
});
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('bands');
}
});
App.BandsRoute = Ember.Route.extend({
model: function() {
return App.Band.find();
}
});
App.BandsNewRoute = Ember.Route.extend({
renderTemplate : function(){
this.render('bands_new',{
into:'application'
});
}
});
Link back to list - which does not work:
App.BandsNewController = Em.ArrayController.extend({
cancel: function() {
this.transitionTo('bands');
}
});
Have a look at the whole app here: https://github.com/pavelsmolka/roommating
(It's hugely inspired by great https://github.com/dgeb/ember_data_example)
I don't believe it, but could it be bug in Ember itself?
I think your "render" call in your BandsNewRoute is messing things up.Try making things go more with Ember defaults. So I would refactor your app to do this:
(working fiddle: http://jsfiddle.net/andremalan/DVbUY/)
Instead of making your own render, all you need to do is create a "bands" template (it can be completely empty except for {{outlet}} if you want) and a "bands.index" template.
<script type="text/x-handlebars" data-template-name="application">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="bands/index">
<h2>Bands Index</h2>
{{#linkTo bands.new}}New Band{{/linkTo}}
</script>
<script type="text/x-handlebars" data-template-name="bands">
<h1>Bands</h1>
<p>
{{#linkTo index}}Start Again{{/linkTo}}
{{#linkTo bands.new}}New Band{{/linkTo}}
</p>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="bands/new">
I'm in new band!
<a {{action "cancel"}}>Cancel</a>
</script>
Your routes also clean up really nicely this way:
App.Router.map(function() {
this.resource('bands', function() {
this.route('new');
});
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('bands');
}
});
App.BandsNewController = Ember.Controller.extend({
cancel: function() {
this.transitionTo('bands');
}
});
I hope that helps!

createRecord called w/o params does not add object to collection

Using:
ember-1.0.0-pre.4.js
ember-data.js REVISION:11
handlebars-1.0.rc.2.js
Please have a look at this jsFiddle illustrating the described problem.
I have a list of items that are displayed in a template. The template contain a linkTo helper that let's the controller add an item to the collection and is shown as a text input on the page.
Adding the item to the collection is done by the controller:
App.TodoItem = DS.Model.extend({
title: DS.attr('string', { defaultValue: "unknown" })
});
App.Router.map(function () {
this.resource('todo_items')
});
App.TodoItemsRoute = Em.Route.extend({
model: function () {
return App.TodoItem.find();
}
});
App.TodoItemsController = Em.ArrayController.extend({
addTodoItem: function () {
App.TodoItem.createRecord();
}
});
If I want the new item to be shown is the list, I have to pass params to createRecord, otherwise the item is not visible. The same behaviour can be reproduced by using Chrome's inspector and then the item can be made visible as follows:
// Open the jsFiddle http://jsfiddle.net/bazzel/BkFYd/ and select 'result(fiddle.jshell.net) in the inspector, then:
var item = App.TodoItem.createRecord();
// Nothing visible yet.
item.set('title', 'Whatever');
// Now the text input appear with the title as its value.
Is this expected behaviour and if so, what am I missing here?
I took time to redo your example the way i feel things should be done properly with Emberjs. You should rather make sure of transaction and properly define your views and then all your issues get taken care of. So here's how i think you should do this
Define a view for the textfield to capture the value being entered or
just bind it to the model property.
Listing items and adding a new item to the list should be done in two different views and should not be mixed together
<script type="text/x-handlebars">
{{outlet}}
<div>
{{outlet 'addItem'}}
</div>
</script>
<script type="text/x-handlebars" data-template-name="todo_items">
{{#linkTo 'todo_items.new'}}Add Todo Item{{/linkTo}}
<ul>
{{#each item in controller}}
<li>
{{#unless item.isNew}}
{{item.title}}
{{/unless}}
</li>
{{/each}}
</ul>
</script>
Define different states for listing items and adding a new one
To benefit from automatic binding of your text field value to the
model property, you need to associate an ObjectController to the TodoItemsNew route
Finally, make use of transaction to create and commit records to the store
window.App = Em.Application.create();
App.TodoItem = DS.Model.extend({
title: DS.attr('string')
});
App.TodoItem.FIXTURES = [{
id: 1,
title: 'Lorem'
}, {
id: 2,
title: 'Ipsum'
}];
App.store = DS.Store.create({
revision: 11,
adapter: DS.FixtureAdapter.create()
});
App.Router.map(function () {
this.resource('todo_items',function(){
this.route('new');
})
});
App.IndexRoute = Em.Route.extend({
redirect: function () {
this.transitionTo('todo_items');
}
});
App.TodoItemsRoute = Em.Route.extend({
model: function () {
return App.TodoItem.find();
}
});
App.TodoItemsNewRoute = Em.Route.extend({
transaction: App.store.transaction(),
setupController:function(controller) {
console.info(controller.toString());
controller.set('content',this.transaction.createRecord(App.TodoItem));
},
renderTemplate: function() {
this.render('addItem',{
into:'application',
outlet:'addItem',
})
},
events: {
addItem: function() {
this.transaction.commit();
this.transitionTo('todo_items');
}
}
});
App.TodoItemsController = Em.ArrayController.extend();
App.TodoItemsNewController = Em.ObjectController.extend();
App.TextField = Ember.TextField.extend({
insertNewline: function () {
this.get('controller').send('addItem')
}
});
Here' is a working version of the example on jsfiddle. Hopefully, i helped with this example clarify some of your issues.
Thank you Ken for answering my question. It indeed feels like a more proper of way of doing this in Ember. However, I still think it's difficult to get the hang of which objects are accessible from where...
Your example inspired me to do a rewrite of my code. I also made some changes to your approach:
I'm not sure if it's the best practice, my I don't create a store instance. Instead I define a Store class.
The content for the TodoItemsNewController is set by calling the model property on the corresponding route.
renderTemplate in the TodoItemsNewRoute only needs the outlet key.
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="todo_items">
{{#linkTo 'todo_items.new'}}Add Todo Item{{/linkTo}}
<ul>
{{outlet "addItem"}}
{{#each controller}}
<li>
{{#unless isNew}}
{{title}}
{{/unless}}
</li>
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="todo_items/new">
{{view Ember.TextField valueBinding="title" placeholder="Enter title"}}
window.App = Em.Application.create();
App.TodoItem = DS.Model.extend({
title: DS.attr('string', {
defaultValue: "unknown"
})
});
App.TodoItem.FIXTURES = [{
id: 1,
title: 'Lorem'
}, {
id: 2,
title: 'Ipsum'
}];
App.Store = DS.Store.extend({
revision: 11,
adapter: DS.FixtureAdapter.create()
});
App.Router.map(function() {
this.resource('todo_items', function() {
this.route('new');
});
});
App.IndexRoute = Em.Route.extend({
redirect: function() {
this.transitionTo('todo_items');
}
});
App.TodoItemsRoute = Em.Route.extend({
model: function() {
return App.TodoItem.find();
}
});
App.TodoItemsNewRoute = Em.Route.extend({
model: function() {
return App.TodoItem.createRecord();
},
renderTemplate: function() {
this.render({
outlet: 'addItem'
});
}
});
App.TodoItemsNewView = Em.View.extend({
tagName: 'li'
});
The updated example is on jsFiddle.
Any reviews are welcome.

{{#each loop}} not working. What would be the right way to get it going

I am following an example at "emberjs.com" which isn't going too well. I have a "GuestController" and "GuestView" within my application. I would like to use the "{{#view}} & {{#each}} to output an object called "guests" from the "GuestView". I am following this online example:
http://emberjs.com/documentation/#toc_displaying-a-list-of-items
fiddle: http://jsfiddle.net/exciter/MjA5A/8/
Here is the code:
APP CODE:
$(function(){
App = Ember.Application.create({
ready: function(){
//alert("APP INIT");
}
});
App.ApplicationController = Ember.Controller.extend();
App.ApplicationView = Ember.View.extend({
templateName: "application",
classNames: ['']
});
App.GuestController = Ember.Controller.extend();
App.GuestView = Ember.View.extend({
guests: [{name:"The Doctor" },
{name:"The Scientist" },
{name:"The Maestro"}]
});
App.initialize();
});
HTML:
<script type="text/x-handlebars" data-template-name="application">
{{#each App.GuestController}}
{{#view App.GuestView}}
{{guests}}
{{/view}}
{{/each}}
</script>
First of all, we use {{each}} block helper to iterate over an array of items, now when you say {{#each GuestController}} the controller should be of type Ember.ArrayController, and the {{#each GuestController}} looks for the content property inside the GuestController which will be used to iterate over, As per the example I think this is what you are trying to implement...Instead if you want to iterate over an Array inside a view check this