Ember Component Bindings Not Propagating - ember.js

I'm expirencing some weirdness with Ember components. First problem I'm seeing is that class attribute binding is not working. I'm also witnessing properties some how being unbound after the first time they are mutated. This is just a simple tabs component that I am building. Here's a repro http://emberjs.jsbin.com/uDUfONi/2/edit
JS
App = Ember.Application.create();
App.IndexRoute = Ember.Route.extend({
model: function() {
return ['red', 'yellow', 'blue'];
}
});
App.IndexController = Ember.Controller.extend({
selectedTab: '',
initialTab: function () {
var name = this.get( 'model' ).get('firstObject');
this.set( 'selectedTab', name );
return name;
}.property()
});
App.MyTabComponent = Ember.Component.extend({
tagName: 'li',
isSelected: false,
tabChanged: function () {
if ( this.get( 'selectedTab' ) !== this.get( 'name' ) ) {
this.set( 'isSelected', false );
} else {
this.set( 'isSelected', true );
}
}.observes('selectedTab'),
checkInitialTab: function () {
if ( this.get( 'initialTab' ) === this.get( 'name' ) ) {
this.set( 'isSelected', true);
} else {
this.set( 'isSelected', false );
}
}.on( 'didInsertElement' ),
actions: {
selectTab: function () {
if ( this.get( 'selectedTab' ) !== this.get( 'name' ) ) {
this.set( 'selectedTab', this.get( 'name' ) );
}
}
}
});
Templates
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{my-tabs model=model initialTab=initialTab selectedTab=selectedTab}}
</script>
<script type="text/x-handlebars" data-template-name="components/my-tabs">
Selected Tab: {{selectedTab}}
<ul class="nav nav-tabs">
{{my-tab name="control" initialTab=initialTab selectedTab=selectedTab}}
{{#each item in model}}
{{my-tab name=item initialTab=controller.initialTab selectedTab=controller.selectedTab}}
{{/each}}
</ul>
</script>
<script type="text/x-handlebars" data-template-name="components/my-tab">
<li {{action selectTab name}} {{bind-attr class="isSelected:active"}}>
{{isSelected}}
{{name}}
</li>
</script>

Okay, had to consult with another Emberino on this one.
It came down to the fact that there were nested li element. When you defined the tagName on the component it injects the component with that tag. Unfortunately it was breaking ember/html/somewhere/Idunno.
http://emberjs.jsbin.com/uDUfONi/10/edit
<script type="text/x-handlebars" data-template-name="components/my-tab">
<a>{{name}}</a>
</script>
App.MyTabComponent = Ember.Component.extend({
tagName: 'li',
classNameBindings: ['isSelected:active', ':clickable'],
isSelected: function(){
return this.get('selectedTab') === this.get('name');
}.property('selectedTab', 'name'),
click: function () {
this.set('selectedTab', this.get('name'));
}
});
instead of the li being defined twice:
<script type="text/x-handlebars" data-template-name="components/my-tab2">
<li {{action selectTab name}}>
{{isSelected}}
<a href>{{name}}</a>
</li>
</script>
App.MyTabComponent = Ember.Component.extend({
tagName: 'li',
.....
});

Yeah, it's definitely the double li that is somehow breaking things. I have no idea exactly how...
I just commented out the tagName : 'li', line and the {{isSelected}} value in the template starts showing the right thing.
http://emberjs.jsbin.com/uDUfONi/12/edit

Related

Ember: Context lost in ember component

I'm having a problem with Ember, although not too sure where the problem lies, whether it's within Handlebars or Ember component.
The issue is when a controller object context is passed in to an ember component, as an argument, the context is undefined. However, logging the object within the handlebars template, immediately preceding the component, shows the correct object (see index and components/component-button templates).
Templates
<script type="text/x-handlebars" data-template-name="application">
<h1>Fiddling</h1>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{#each sections}}
{{#each buttons}}
{{log 'source in template' ../source}}
{{component-button source=../source options=this}}
{{/each}}
{{/each}}
</script>
<script type="text/x-handlebars" data-template-name="components/component-button">
{{log 'source in component' source}}
<button {{action 'options.action'}}>{{options.label}}</button>
</script>
Application
App = Ember.Application.create( {} );
/**
* Component: Button
*/
App.ButtonComponent = Ember.Component.extend( {
tagName: 'button',
click: function click() {
this.get( 'source' ).send( this.get( 'options.action' ) );
}
} );
/**
* Controller: Application
*/
App.IndexController = Ember.ObjectController.extend( {
sections: Ember.A(),
actions: {
doSomething: function() {
window.console.log( 'hooray!' );
}
},
setup: function setup() {
var source = this;
var section = Ember.Object.create( {
source: source,
buttons: Ember.A( [
Ember.Object.create( {
label: 'Do something!',
action: 'doSomething'
} )
] )
} );
this.sections.pushObject( section );
}.on( 'init' )
} );
App.IndexRoute = Ember.Route.extend( {
model: function() {
return { name: 'foo' };
}
} );
See fiddle
Instead of referring to ../source in the each loop, do the following:
<script type="text/x-handlebars" data-template-name="index">
{{#each section in sections}}
{{#each section.buttons}}
{{log 'source in template' section.source}}
{{component-button source=section.source options=this}}
{{/each}}
{{/each}}
</script>
In the first each 'section' is defined, which can be used in nested each statements to refer to the section.

What is the correct way to target a sibling element in ember?

My current approach works, but it relies on jQuery rather than targeting the element directly. This feels less than ideal. Is there a standard way of doing this in ember?
App.facetGroup = Em.View.extend({
templateName: "facet-group",
actions: {
showList: function(e) {
var id = '#' + this.get('elementId');
$(id).children('.facets-list').slideToggle(100)
}
}
});
The facet-group template:
<h3 {{action showList target="view"}} class="facet-group-heading">{{view.displayName}}</h3>
// Facet lists are hidden by default
<ul class="facets-list">
{{#each view.facets }}
{{view this}}
{{/each}}
</ul>
Wouldn't you be better of by creating a ContainerView with two child views: [h3 ..] and [ul ..]? Also you can target the view's jquery element by using: this.$(), instead of this.get('elementId')
Edit:
Something like this should work:
App.FacetGroupView = Ember.ContainerView.create({
childViews: ['header', 'list'],
header: Ember.View.create(
tagName: 'h3',
// templateName: 'facet-group/header' or
template: Ember.Handlebars.compile('
{{view.displayName}}
'),
classNames: ['facet-group-heading'],
click: function() {
// Access the list view element
this.list.$().slideToggle(100);
}
)
list: Ember.View.create(
tagName: 'ul',
classNames: ['facets-list'],
// templateName: 'facet-group/list' or
template: Ember.Handlebars.compile('
{{#each view.facets}}
{{view this}}
{{/each}}
'),
didInsertElement: function() {
// Hide facet list by default
this.$().hide();
}
)
});
You can also target the sibbling like
actions: {
showList: function() {
this.$('.facets-list').slideToggle(100)
}
}
Demo Fiddle

template not bind a model in Ember

I will try to bind model,controller and template in ember
Here is my js
App = Ember.Application.create({});
App.Person = Ember.Object.extend({
firstName: "r",
lastName: "issa"
});
App.TestingRoute = Ember.Route.extend({
model: function () {
return App.Person.create();
},
setupController: function (controller, model) {
controller.set("model", model);
}
});
App.TestingController = Ember.ObjectController.extend({
submitAction: function () {
alert("My model is :" + this.get("model"));
}
});
my template is :
<script type="text/x-handlebars" data-template-name="application">
{{render testing}}
</script>
<script type="text/x-handlebars" data-template-name="testing">
{{input valueBinding="model.firstName"}}
{{input valueBinding="model.lastName"}}
<button {{action submitAction target="controller"}} class="btn btn-success btn-lg">Pseudo Submit</button>
<p>{{model.firstName}} - {{model.lastName}}</p>
</script>
what s wrong why model not binding in template and alert retunn model is null
Your setupController and model methods from TestingRoute aren't being called. Because your controller is created by the render view helper, not by a transition.
For example using the following works:
template
<script type="text/x-handlebars" data-template-name="testing">
{{input valueBinding="model.firstName"}}
{{input valueBinding="model.lastName"}}
<button {{action submitAction target="controller"}} class="btn btn-success btn-lg">Pseudo Submit</button>
<p>{{model.firstName}} - {{model.lastName}}</p>
</script>
javascript
App = Ember.Application.create({});
App.Router.map(function() {
this.route('testing', { path: '/' })
});
App.Person = Ember.Object.extend({
firstName: "r",
lastName: "issa"
});
App.TestingRoute = Ember.Route.extend({
model: function () {
return App.Person.create();
},
setupController: function (controller, model) {
debugger
controller.set("model", model);
}
});
App.TestingController = Ember.ObjectController.extend({
actions: {
submitAction: function () {
alert("My model is :" + this.get("model"));
}
}
});
Here is the fiddle http://jsfiddle.net/marciojunior/8DaE9/
Also, the use of actions in controllers is deprecated in favor of using the actions: { ... } object

How to use CollectionView inside View to not use the defaultContainer in Ember JS

I want to insert CollectionView into View. It works but displays:
DEPRECATION: Using the defaultContainer is no longer supported. [defaultContainer#lookup]
How correctly insert CollectionView in View?
App = Ember.Application.create();
App.Router.map(function() {
this.route("index", { path: "/" });
});
App.FirstView = Em.View.extend({
templateName: 'first'
});
App.SecondView = Em.View.extend({
templateName: 'second'
});
App.MyCollection = Em.CollectionView.extend({
content: ['f','s'],
createChildView: function(viewClass, attrs){
if (attrs.content == 'f') {
viewClass = App.FirstView ;
};
if (attrs.content == 's') {
viewClass = App.SecondView ;
};
return this._super(viewClass, attrs);
}
});
App.IndexView = Em.View.extend({
myChildView: App.MyCollection.create()
});
templates:
<script type="text/x-handlebars">
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
{{view view.myChildView}}
</script>
<script type="text/x-handlebars" data-template-name="first">
search
</script>
<script type="text/x-handlebars" data-template-name="second">
client
</script>
Sorry for my english, i am from Russia and understand it a little))
Your IndexView is directly instantiating the view. This style of instantiation is deprecated with nested views, so as to allow child views to get their parent Container.
Change that to declare the myChildView directly, and the deprecation warning will go away.
App.IndexView = Em.View.extend({
myChildView: App.MyCollection
});

Trigger an action in a text field from a button

I'm looking for advice on how to trigger this view function insertNewLine from a button (see view and template below). I'm guessing there's probably a better way to structure this code. Thanks for your help.
// view
App.SearchView = Ember.TextField.extend({
insertNewline: function() {
var value = this.get('value');
if (value) {
App.productsController.search(value);
}
}
});
// template
<script type="text/x-handlebars">
{{view App.SearchView placeholder="search"}}
<button id="search-button" class="btn primary">Search</button>
</script>
You could use the mixin Ember.TargetActionSupport on your TextField and execute triggerAction() when insertNewline is invoked. See http://jsfiddle.net/pangratz666/zc9AA/
Handlebars:
<script type="text/x-handlebars">
{{view App.SearchView placeholder="search" target="App.searchController" action="search"}}
{{#view Ember.Button target="App.searchController" action="search" }}
Search
{{/view}}
</script>
JavaScript:
App = Ember.Application.create({});
App.searchController = Ember.Object.create({
searchText: '',
search: function(){
console.log('search for %#'.fmt( this.get('searchText') ));
}
});
App.SearchView = Ember.TextField.extend(Ember.TargetActionSupport, {
valueBinding: 'App.searchController.searchText',
insertNewline: function() {
this.triggerAction();
}
});