emberjs itemcontroller property scope - ember.js

I need an explanation for the usage of an itemcontroller in emberJS.
I created a handlebars template that looks like this:
{{#each thing in controller itemController="itemController"}}
{{view "testview" contentBinding="thing"}}
{{/each}}
The testview creates a html table and within the testview I use a second view in a each loop which creates several tr:
{{each item in view.content.thing}}
{{view 'trview' contentBinding="item"}}
{{/each}}
In addition to that I added a property "listOfProperties" (Ember.A()) to the itemController.
I use the click function of the trview to add a value to the "listOfProperties" array of the itemController.
And here I receive an error: If I click on a tr, the value is added to each itemControllers "listOfProperties" array and not only to one "things" itemController.

I'm going to guess since you didn't include your item controller, but it's likely you're running into a reference issue. Array's are a reference and as such are shared across instances of item controllers.
App.ItemController = Em.ObjectController.extend({
setup: function(){
this.set('listOfProperties', []);
}.on('init'),
listOfProperties: null,
});

Related

Ember 2.3 how to use itemControllers in each loops?

Before anyone brings up components, I must state that I am aware that Ember is moving away from controllers and views completely and adopting the component structure. Right now, I am compelled to use controller/view in ember2.3 using the legacy-controller and legacy-view addons that have been provided here:
https://github.com/emberjs/ember-legacy-controllers
https://github.com/emberjs/ember-legacy-views
as part of the process to upgrade to Ember 2.3 (from 1.7).
Now, I have a route called recordTypes, which lists all recordTypes. in the legacy code, each recordType was then associated with an itemController 'recordType'. Like so:
{{#each result in searchResults itemController="recordType"}}
...
{{/each}}
Surprisingly, this legacy syntax for Ember did not render anything to the page, but the following one did:
{{#each searchResults itemController="recordType" as |result| }}
...
{{/each}}
The itemController recordType is a legacy Object Controller and the recordTypes controller itself is a legacy Array Controller.
Now, for each result I have a few actions that can be performed. For example, on clicking the result, the editResultName action was to be fired. This action, in the legacy code, was in the recordType controller. Therefore, clicking the item in the recordTypes page would then defer this action to the recordType controller, which would then happily handle the rest.
This is not being fired in ember2.3, even with the legacy controllers. What surprises me more is that this code can be found in ember-legacy-controller.js
export default {
name: 'ember-legacy-controllers',
initialize: function() {
/**
Adds support for ArrayController in the legacy {{each}} helper
*/
Ember._LegacyEachView.reopen({
_arrayController: computed(function() {
var itemController = this.getAttr('itemController');
var controller = get(this, 'container').lookupFactory('controller:array').create({
_isVirtual: true,
parentController: get(this, 'controller'),
itemController: itemController,
target: get(this, 'controller'),
_eachView: this,
content: this.getAttr('content')
});
return controller;
}),
_willUpdate(attrs) {
let itemController = this.getAttrFor(attrs, 'itemController');
if (itemController) {
let arrayController = get(this, '_arrayController');
set(arrayController, 'content', this.getAttrFor(attrs, 'content'));
}
}
});
}
};
Here, it does have a line that references the itemController. However, when this list of searchResults is rendered, and a result is clicked, the error I get is this:
Nothing handled the action 'editResultName'. If you did handle the action, this error can be caused by returning true from an action handler in a controller, causing the action to bubble.
The action is there for sure, but nothing in the itemController is being recognised. Unfortunately a lot of the legacy code I am updating has itemController loops and therefore it would be immensely helpful to be able to use itemController for the time being.
How can I use itemController like it used to be implemented?
Replacing an itemController.
Create a component from the contents inside the each helper. The itemController would become the js side of the component and the template code the template
From this:
{{#each result in searchResults itemController="recordType"}}
<span>result: {{result.title}}</span>
{{/each}}
To this:
{{#each searchResults as |result| }}
{{result-list-item result=result}}
{{/each}}

Ember: property in array controller computed from property of item controller

I have a typical ArrayController/ObjectController setup as follows:
App.PlayersController = Ember.ArrayController.extend({
itemController: 'playerItem',
visibleCount: function(){
//How do I compute this property?
}.property(...),
});
App.PlayerItemController = Ember.ObjectController.extend({
needs: 'players',
visible: function(){
...
}).property('...')
})
The visible property is computer on the PlayerItemController, but I want to have a global count of visible players from the visibleCount in the PlayersController. How do I achieve this?
Here's what I've tried:
visibleCount: function(){
return this.get('content').filter(function(p){
return p.get('visible')
}).length;
}.property('content.#each.visible'),
This doesn't work, as p.get('visible') returns undefined every time and the property returns 0.
You can trigger this completely off the array controller instance itself, since that's where the itemController is defined. Any time you attempt to iterate the this inside the array controller you will get the individual item controllers.
This makes just as much since when you think about it in the context of the template. If you iterate the controller, you get the item Controllers
{{#each item in controller}}
yay I have an item controller
{{/each}}
whereas if you just iterate the model/content you just get that record back
{{#each item in model}}
boo, boring model
{{/each}}
App.PlayersController = Ember.ArrayController.extend({
itemController: 'playerItem',
visibleItems: Em.computed.filterBy('','visible', true),
// you don't need this below, you could just do, `visibleItems.length` in the template
visibleCount: Em.computed.alias('visibleItems.length'),
// showing off getting item controller
randomProp: function(){
this.forEach(function(ic){
console.log(ic.get('visible'));
});
}.property('#each.visible')
});
Example: http://emberjs.jsbin.com/dalazawe/1/edit
It's really cool if you think of it in the way it's implemented. Essentially the array controller overrides how it returns items from the controller to you. When you attempt to get objectAt(1), if you've defined an itemController it will wrap the underlying object in the itemController and return it. Or if you haven't defined an itemController, it just returns the object to you. Most people are stuck on it just being how it's implemented in the template but that's just half of it, it's how the array controller returns objects to you.
Example: http://emberjs.jsbin.com/dalazawe/2/edit

Finding a component and changing its property in Ember.js

I have a template which creates a component for every record it has in the model. I want to find a component and update one of its property at runtime based on an event from some other template. How to find a particular component inserted in the DOM.
{{#each}}
{{my-name}}
{{/each}}
<script type="text/x-handlebars" data-template-name="components/my-name">
Hi, my name is {{name}}.
</script>
var App = Ember.Application.create();
App.IndexRoute=Ember.Route.extend({
model:function(){
return dummyNames;
}
});
var dummyName={[name='John', name='Jane', name='Jade']};
This code would display the names on the screen. Now I have another template called change.
<script type="text/x-handlebars" data-template-name="change">
<button {{action 'changeNow'}}></button>
</script>
App.ChangeController=Ember.Controller.extend({
actions:{
changeNow:function(){
//Here I want to find the component where the name is Jane and change it to Kate. How to do this?
}
}
});
here is a jsbin i prepared for you.
http://emberjs.jsbin.com/sanukoro/3/edit
Component has its own scope. and if the {{name}} are displayed in you component which I assume you have rendered in index template. that means you have passed that property to the component.
something like this from guide
{{blog-post title=name}}
passing properties to components
So basicall the property you want to modify is of IndexController.
You can directly modify the IndexController property(dummynames) and the change will be reflected in you component because of data bindings.
Since you want to do it in al together different controller you will have to declare dependency on IndexController in ChangeController.
Managing dependencies
App.ChangeController=Ember.Controller.extend({
needs: ['index'] //dependency
names: Ember.computed.alias("controllers.content") //now get properties of Index controller
actions:{
changeNow:function(){
//here you can find name jane and replace it with to kate
}
}
});
You may have to look at addArrayObserver to track changes closely.

dynamic outlet name in ember js

Requirement: There will be few buttons, and on clicking every button the immediate outlet will be rendered with a view. (not the other outlets present in the page)
Suppose I'm creating outlets in #each.
{{#each item in model}}
<#link-to 'passenger' item.id>Open Corresponding Outlet </link-to>
{{outlet item.id}}
{{/each}}
and from back i'm rendering the outlet:
model: function (params) {
return [
{ id: params.passenger_id}
]
},
renderTemplate: function () {
this.render({ outlet: this.get('controller.model')[0].id });
},
This just doesn't work.
Can anyone help me to do that?
You can't create dynamic named outlets, and it would break the url representing the page you are viewing concept which the core members preach heavily.
You should just render in the template using the render helper, and use
{{#each item in model}}
{{render 'something' item}}
{{/each}}
And inside the something controller/template you can add additional logic for how you'd like it to interact.
Additionally you could just add another resource under your passenger route and ad an outlet which hosts that information when it's clicked.
Not sure if you're still looking for a solution, as #kinpin2k mentioned you can't use dynamic outlets, but perhaps a dynamic partial would help.
Like so:
{{partial someTemplateName}}
Where someTemplateName can be any computed property in your controller. If the property returns falsy then nothing will be displayed.
Here's the reference: http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#toc_bound-template-names

Ember.js arraycontroller call from view

I might be using this all wrong, but:
I've got an ArrayController representing a collection of products. Each product gets rendered and there are several actions a user could take, for example edit the product title or copy the description from a different product.
Question is: how do you interact with the controller for the specific product you're working with? How would the controller know which product was being edited?
I also tried to create an Ember.Select with selectionBinding set to "controller.somevar" but that also failed.
I think the most important thing you need to do, is first move as much logic as you can away from the views, and into controllers.
Another thing that would be useful in your case, is to have an itemController for each product in the list. That way, you can handle item specific logic in this item controller.
I don't have enough information to understand your architecture, so I will make a few assumptions.
Given you have the following ProductsController:
App.ProductsController = Ember.ArrayController.extend();
You need to create a ProductController that will be created to wrap every single product on its own.
App.ProductController = Ember.ObjectController.extend();
You need to modify your template as follows:
{{#each controller itemController="product"}}
<li>name</li>
{{/each}}
Now every product in your list will have its own ProductController, which can handle one product's events and will act as the context for every list item.
Another option:
If you will only be handling one product at a time, you can use routes to describe which product you are working with:
App.Router.map(function() {
this.resource('products', { path: '/products' }, function() {
this.resource('product', { path: '/:product_id' }, function() {
this.route('edit');
});
});
});
And create a controller for editing a product:
App.ProductEditController = Ember.ObjectController.extend();
And your list items would link to that product route:
{{#each controller}}
<li>{{#linkTo "product.edit" this}}name{{/linkTo}}</li>
{{/each}}
If you define itemController on your ProductsController you don't need to specify that detail in your template:
App.ProductsController = Em.ArrayController.extend({
itemController: 'product',
needs: ['suppliers'],
actions: {
add: function() {
// do something to add an item to content collection
}
}
});
App.ProductController = Em.ObjectController.extend({
actions: {
remove: function() {
// do something to remove the item
}
}
});
Use a collection template like this:
<button {{action="add"}}>Add Item</button>
<ul>
{{#each controller}}
<li>{{name}} <button {{action="remove"}}>x</button></li>
{{/each}}
</ul>
The Ember documentation describesitemController here:
You can even define a function lookupItemController which can dynamically decide the item controller (eg based on model type perhaps).
The thing I found when rendering a collection wrapped in an ArrayController within another template/view is the way #each is used. Make sure you use {{#each controller}} as Teddy Zeeny shows otherwise you end up using the content model items and NOT the item controller wrapped items. You may not notice this until you try using actions which are intended to be handled by the item controller or other controller based content decoration.
When I need to nest an entire collection in another view I use the view helper as follows to set the context correctly so that any collection level actions (eg an add item button action) get handled by the array controller and not by the main controller setup by the route.
So in my products template I would do something like this to list the nested suppliers (assuming your route for 'product' has properly the 'suppliers' controller):
{{view controller=controllers.suppliers templateName="products/suppliers"}}
The suppliers template just follows the same pattern as the template I show above.