Obtaining component elementId in parent template - ember.js

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)

Related

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).

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.

Ember.js controller needs and child controller

I'm having trouble targeting a child controller from the parent controller.
Having this:
<script type="text/x-handlebars" data-template-name="parent">
<h1>Parent controller</h1>
{{control "child"}}
</script>
<script type="text/x-handlebars" data-template-name="child">
<h2>Child controller</h2>
{{controller}}
</script>
App.ParentController = Ember.Controller.extend({
needs: ["child"],
applyActionOnChild:function(){
this.get('controllers.child').someAction();
}
});
App.ParentView = Ember.View.extend({});
App.ChildController = Ember.Controller.extend({
someAction:function(){
//Called but not the right instance
};
});
App.ChildView = Ember.View.extend({});
The needs feature of ember seems to work when a child controller calls the parent controller but not for the other way around...
If I place a breakpoint in the someAction method, I can see I have a different object reference than the one displayed on the child view...
Anyone knows how this could work?
Thanks in advance
In your example you specify {{controller "child"}}, did you mean {{control "child"}}? If not hard to tell what you're trying to do.
The needs feature is for wiring together controller singletons, but The control helper renders template in context of a new controller/view pair. You can't use needs to access these controllers, since there'd be no way for needs to know which instance you meant.
Sounds like instead you want to render "child" template in context of the one true childController singleton. In that case use the render helper: {{render "child"}}

Uviews are not getting connected to controller

Ember has a Application which has ApplicationView, ApplicationController and 'application' named template and 'main' named outlet and all these connect automatically.
eg.
App.ApplicationView = Ember.View.extend();
and
App.ApplicationController = Ember.Controller.extend();
so whenever my application template renders its default controller is an automatic instance of
App.ApplicationController
whose properties i can access in the template. But why does it not works with other views and controllers, i.e. if I have
App.SongView = Em.View.extend()
and
App.SongController = Em.Controller.extend()
these two do not connect. I can use any property of
App.SongController
in my song template.
I can use like :
{{view App.SongView}}
and in the template:
<script type='text/x-handlebars' data-template-name='song'>
{{name}}
</script>
and if i have a name property in App.SongController then it wont get picked up because its not connected to the View.
although i can do it like this
{{view App.SongView controllerBinding='App.songController'}}
but this requires the instance of App.songController in my js file and also using this approach we are hard-coding the controller to the template.
What is the best way for this?
Update
I am also attaching a js fiddle for my problem here:
http://jsfiddle.net/anshulguleria/K6KPJ/
If you want to render a template and its associated controller and view, you can use the {{render}} template. It works similarly to this.render in the router.
{{render "song" song}}
This example will render the song template with an instance of App.SongController and App.SongView. It will set the song controller's model to the value of song in the current context.
Here is a working JSBin that illustrates how this works.

Can't get value from a view's controller within a containerview in Ember

Hello Ember jedi masters,
I'm learning Ember's framework and get some confuses while using it with handlebars helpers.
Firstly I created some view templates in my js and html and used a containerView to group those templates.
But I'm having a trouble that I can't display the value I described in my controllers of those template views.
My HTML part is like this:
<script type="text/x-handlebars" data-template-name="main">
<p>this is main template</p>
{{outlet nav}}
</script>
<script type="text/x-handlebars" data-template-name="nav">
</script>
<script type="text/x-handlebars" data-template-name="child">
<p>this is the child in nav, value is {{value}}</p>
</script>
Here's my sample code on jsfiddle (including the JS part):
http://jsfiddle.net/9K7D4/
my question is:
while the child view is rendered from container view, I couldn't get the value which is defined in my child view's controller. I must missed something in the document.. just couldn't figure it out..
Thanks for helping me!
In your example, though the child controller instantiated during application initialization, it is not connected as the controller of the child view (I think there something missing in the framework).
Anyway, if you want to refer it in the child view, you have to lookup through the router with valueBinding: 'App.router.cController.content.value'. Note I'm using lowercase, as conventionnally, ember will create an instance of XxxController as xxxController.
Then in the template, as you want to use a property from the view itself, you must use the view keyword in order to do it.
see http://jsfiddle.net/9K7D4/14/