Finding a component and changing its property in Ember.js - 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.

Related

Obtaining component elementId in parent template

I would like to obtain component auto-generated elementId in parent template, e.g.
<script type="text/x-handlebars" data-template-name="index">
my-component elementId: {{myComponentElementId}}
{{my-component}}
</script>
Is there a simple way to do this without altering parent controller or my-component?
elementId is being generated during initialization of the component. You can be sure that the ID is set after the component is fully inserted into DOM. Therefore, you can use didInsertElement action like this:
some-example elementId: {{myComponentElementId}}
{{some-example elementIdProxy=myComponentElementId}}
didInsertElement: function() {
this.set("elementIdProxy", this.get("elementId"));
}
Thus, you proxy the elementId via some other binding (elementIdProxy in this situation). I strongly recommend not to set up the ID explicitly, as it must be unique within the app. Ember takes care about that, so it's not a good idea to reinvent the wheel.
Fully working example in JSBin is here
In your bin you are trying to pass an id but not specified the value on the controller. Set the id value in the setupCotroller hook on the controller and that value will be used as the id.
Otherwise you will need to pass the id up via actions. Here is the link.
The problem (ie the source of your error message) is you're trying to override the component's elementId property via attribute binding. I have run into this issue when trying to pass in id. A simple work around is to use _elementId or any other word that isn't elementId. Check the JSBin
I was able to access elementId by:
explicitly naming component using its viewName attribute
accessing component by its name from parent template view.[componentName].elementId
Example:
<script type="text/x-handlebars" data-template-name="index">
my-component elementId: {{view.myComponent.elementId}}
{{my-component viewName="myComponent"}}
</script>
JsBin
NOTE
With introduction of Glimmer rendering engine (Ember version >=1.13), accessing elementId via viewName is not supported any more.
Instead, component's elementId should be explicitly set to specific value, e.g.
Example:
<script type="text/x-handlebars" data-template-name="index">
my-component elementId: index-my-component
{{my-component id="index-my-component"}}
</script>
In this case, it is up to developer to make sure that elementId is unique within page DOM. If template is to be rendered multiple times, elementId should be concatenated from parent component elementId and some predefined id (see JsBin)

Ember.js How to bind isSelected class to the clicked view?

I have some views which will expand and show details when clicked.
For now, all the views can be clicked and expand, but the question is
How to expand only the latest clicked view?
For example, when I clicked view #1, it expand. So when I clicked view #2, the view #1 will collapse and view #2 expand etc.
I know we can bind a isSelected classname to the clicked view, but how do we tell the view to check "If any other view is selected" ?
Do we use CollectionView ? But how?
FYI this is the working JSBin.
First of all, I would change view to component. Although views have their valid use-cases, you are usually better off with a component.
Also, if you think about it, it makes sense that someone outside of the component would need to know which component was clicked last. That outside actor could be the controller, which could have a property called lastComponentClicked (which initially starts out as null)
App.IndexController = Ember.ArrayController.extend({
lastClickedComponent: null
});
Then, you can pass that property into each component and the property becomes bound between the controller and all the components as in:
<script type="text/x-handlebars" data-template-name="index">
{{#each content in model}}
{{ x-box content=content lastClickedComponent=lastClickedComponent}}
{{/each}}
</script>
So far, so good. Now, for the component itself:
App.XBoxComponent = Em.Component.extend({
classNames: ['box'],
isSelected: function(){
return this.get('lastClickedComponent') === this._uuid;
}.property('lastClickedComponent'),
click: function(){
this.set('lastClickedComponent', this._uuid);
}
});
Every time it is clicked, you can set a lastClickedComponent property, which is bound between ALL the components and the controller and thus will get reset every time. You can just set it to a value unique to the component, for example this._uuid.
isSelected computed property can then just check if lastClickedComponent property is that of THIS component, in which case the content you need to show will be expanded.
<script type="text/x-handlebars" id="components/x-box">
{{content}}
{{# if isSelected }}
<div>LAST SELECTED</div>
{{/if}}
</script>
Working solution here

emberjs itemcontroller property scope

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,
});

Custom view helper in Ember.js, "You can't use appendChild outside of the rendering process"

I want to bind my custom view's class to a controller property.
[javascript]
App.IndexController = Ember.Controller.extend({
headerClass: "a"
});
App.TestHeaderView = Ember.View.extend({
classNames: ["test-header"],
classNameBindings: ["headerClass"],
headerClass: null,
templateName: "views/test-header"
});
[templates]
<script type="text/x-handlebars" data-template-name="index">
{{view App.TestHeaderView text="view helper" headerClass=controller.headerClass }}
<hr />
{{input value=headerClass}}
</script>
<script type="text/x-handlebars" data-template-name="views/test-header">
<small>{{view.text}}</small>
</script>
The result is predictable: everything works. I can enter the class name in the text box and see it reflected in the view.
So now I want to extend this and add my own helper that wraps the {{view}} call.
[javascript]
Ember.Handlebars.helper("test-header", function (options) {
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
});
[templates]
<script type="text/x-handlebars" data-template-name="index">
{{test-header text="custom helper" headerClass=controller.headerClass}}
</script>
Nothing special right? Except, I keep getting this:
Uncaught Error: You can't use appendChild outside of the rendering process
For full working jsbin, click here.
It seems this should work. I'm just wrapping the ember's view helper pretty much exactly. What am I missing?
I figured it out.
The trick is in the contexts array in the options hash.
When you call {{view App.MyView}} from handlebars, Ember's view helper gets in its options.contexts array the "context" in which it should search for "App.MyView" property - usually the current controller. In this case, "App.MyView" will be resolved regardless of the context, but I guess Ember keeps the context around and uses it to resolve bound properties.
When I called:
{{test-header text="custom helper" headerClass=controller.headerClass}}
there was no first argument from which to draw the context. Therefore, when I passed the call along to the view helper:
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
... there was no context passed along in the options.contexts array.
The way I fixed this is:
Ember.Handlebars.helper("test-header", function (options) {
options.contexts = [this].concat(options.contexts);
return Ember.Handlebars.helpers.view.call(this, App.TestHeaderView, options);
});
IMO Ember should do a better job here. They should either figure out a context from reference, or throw an error (a preferred option).

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