How to access the content of a controller based on a property name - ember.js

I am trying to reuse code in my controllers / templates. In one of my templates, I have the following:
{{#if controllers.nodesIndex.content.isUpdating}}
This is working fine, but makes this template very specific. I want to generalize this. I would like to do somthing like:
{{#if controllers.{{indexController}}.content.isUpdating }}
Where indexController is actually a config parameter in my controller:
indexController : 'nodesIndex',
The syntax used is not working, and I do not know if there is a way to do what I am trying to do.
Another thing I have tried is simply:
{{#if isUpdating}}
And I have defined this as a computed property in the controler. This is the generic setup ("base class"):
getIndexController : function () {
return this.get('controllers.' + this.indexController);
},
isUpdating : function () { return this.getIndexController().get('content').isUpdating; }.property(),
But I am unable to tell property() which other properties this computed property depends on, since they are actually configurable, as returned by the getIndexController() function.
Any ideas on how to access the content of a "dynamic" controller?

I think that you are going to the wrong way. Ember provide some options to you share templates between controllers.
Here I made a example with partial
http://jsfiddle.net/marciojunior/KxM9k/
<script type="text/x-handlebars" data-template-name="application">
<h1>Partial example</h1>
{{#linkTo "index"}}Index{{/linkTo}}
{{#linkTo "other"}}Other{{/linkTo}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="index">
<h2>Index template</h2>
{{partial "updatingMessage"}}
</script>
<script type="text/x-handlebars" data-template-name="other">
<h2>Other template</h2>
{{partial "updatingMessage"}}
</script>
All options avaliable are in that link http://emberjs.com/guides/templates/rendering-with-helpers/

Having a property on your controller that is the name of the "dynamic" controller is a good place to start. Why not just make your property depend on that?
getIndexController : function () {
return this.get('controllers.' + this.indexController);
},
isUpdating : function () {
return this.getIndexController().get('content').isUpdating;
}.property('indexController'),
If you think of "getIndexController" as a helper function then it makes sense. Rolling the two functions together makes it easier to see that the indexController attribute is the only real property that needs to be observed:
isUpdating : function () {
return this.get('controllers.' + this.indexController).get('content').isUpdating;
}.property('indexController'),

I have implemented this in an indirect way, with an observer:
isUpdatingChanged: function () {
this.getTopController().set('isUpdating', this.get('content').get('isUpdating'));
}.observes('content.isUpdating'),

Related

Ember Controller method to manipulate parameters and return a value to the view?

{{#each accounts as |account|}}
<li title={{someMethodReturnsValue(account)}}>{{account.accName}}</li>
{{/each}}
Helpers can be used but just trying to find a solution within controller itself. Is there any way ?
Thank you.
Handlebars doesn’t really permit function calls in that sense, it would be more idiomatic to create a derived property in the controller that you iterate through.
Template:
{{#each accountsWithTitles as |account|}}
<li title={{account.title}}>{{account.accName}}</li>
{{/each}}
Controller:
accountsWithTitles: computed('accounts.[]', function() {
return this.accounts.map(account => {
return {
...account,
title: someMethodReturnsValue(account),
};
});
})
That computed property is speculative of course, but gives the general idea.

How can I add a class in ember js

<script type="text/x-handlebars">
<div class="wrapper">
<div class="sideMenu">
{{#link-to 'home'}}Home{{/link-to}}
{{#link-to 'posts'}}Posts{{/link-to}}
</div>
<div class="content">
{{outlet}}
</div>
</div>
</script>
I am new to ember js. How can I add a class on 'content' class each time when view changes.
We do something like this:
Ember.Route.reopen({
activate: function() {
var cssClass = this.toCssClass();
// you probably don't need the application class
// to be added to the body
if (cssClass !== 'application') {
Ember.$('body').addClass(cssClass);
}
},
deactivate: function() {
Ember.$('body').removeClass(this.toCssClass());
},
toCssClass: function() {
return this.routeName.replace(/\./g, '-').dasherize();
}
});
It would add a class to the body (in your case just use content), that is the same as the current route.
#torazaburo had some excellent points about #Asgaroth (accepted) answer, but I liked the idea of not having to write this same functionality over and over again. So, what I am providing below is a hybrid of the two solutions plus my own two cents and I believe it addresses #torazaburo concerns regarding the accepted answer.
Let's start with the 2nd point:
I also don't like the idea of polluting Ember.Route
Can you pollute Ember.Route without polluting Ember.Route? (Huh?) Absolutely! :) Instead of overwriting activate, we can write our own function and tell it to run .on(activate) This way, our logic is run, but we are not messing with the built-in/inherited activate hook.
The accepted answer is very procedural, imperative, jQuery-ish, and un-Ember-like.
I have to agree with this as well. In the accepted answer, we are abandoning Ember's data binding approach and instead fall back on the jQuery. Not only that, we then have to have more code in the deactivate to "clean up the mess".
So, here is my approach:
Ember.Route.reopen({
setContentClass: function(){
this.controllerFor('application').set("path", this.routeName.dasherize());
}.on('activate')
});
We add our own method to the Ember.Route class without overwriting activate hook. All the method is doing is setting a path property on the application controller.
Then, inside application template, we can bind to that property:
<div {{bind-attr class=":content path"}}>
{{outlet}}
</div>
Working solution here
Just bind the currentPath property on the application controller to the class of the element in the template:
<div {{bind-attr class=":content currentPath"}}>
{{outlet}}
</div>
In case you're not familiar with the {{bind-attr class= syntax in Ember/Handlebars:
the class name preceded with a colon (:content) is always added to the element
properties such as currentPath result in the current value of that property being inserted as a class, and are kept dynamically updated
To be able to access currentPath in a template being driven by a controller other than the application controller, first add
needs: ['application']
to the controller, which makes the application controller available under the name controllers.application, for use in the bind-attr as follows:
<div {{bind-attr class=":content controllers.application.currentPath"}}>
You may use currentRouteName instead of or in addition to currentPath if that works better for you.
The class name added will be dotted, such as uploads.index. You can refer to that in your CSS by escaping the dot, as in
.uploads\.index { }
Or, if you would prefer dasherized, add a property to give the dasherized path, such as
dasherizedCurrentPath: function() {
return this.('currentPath').replace(/\./g, '-');
}.property('currentPath')
<div {{bind-attr class=":content dasherizedCurrentPath"}}>
This has been tested in recent versions of ember-cli.

Different layouts depending on sub resources

Sorry if this is a really obvious questions but I have the following routes:
Web.Router.map(function () {
this.resource('orders', function(){
this.resource("order", {path:":order_id"});
});
});
And for my orders template I have something like:
<div class="someclass">
{{outlet}}
</div>
And what I want todo is:
{{#if onOrderRoute}}
<div class="someclass">
{{outlet}}
{{else}}
<div class="someotherclass">
{{/if}}
</div>
I was wondering what the best way of doing this is, or am I mising something?
There are multiple ways to accomplish this. The view has a layoutName property you can use to specify your layout. Another option is to specify a property on your child view, and then your template can bind to that by using the view property.
For example:
Web.OrderView = Ember.View.extend({
childView: true
);
Then, in your template you bind to view.childView
{{#if view.childView}}
<!-- code goes here -->
{{/if}}
Further, you can even create a mixin and then just inject that mixin into every view.
Web.ChildViewMixin = Ember.Mixin.create({
childView: true
});
Web.ChildView = Ember.View.extend(ChildViewMixin, {
});

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

Calling a handlebars block helper from another helper

How can I call a handlebars block helper from another helper in ember.
I am interested in converting the following in a bound helper.
{{#each link in sideMenuLinks}}
<li class="navigation page">
{{#linkTo ROUTE_VARIABLE link.linkToRouteContext}}
{{{link.iconTag}}}<i class="icon-right-open"></i>{{link.linkText}}</a>
{{/linkTo}}
</li>
{{/each}}
Note that I will have to call a {{#linkTo}} block inside the helper and also change ROUTE_VARIABLE with a value from the property link.linkToRoute.
The Ember handlebars helpers are placed at Ember.Handlebars.helpers. You can call them with Ember.Handlebars.helpers.{helperName}.call.
However, The above looks like a dynamic partial/view style helper. I would suggest creating a Handlebars View helper for it. The syntax is similar but you pass in a View class to the helper.
Ember.Handlebars.helper('sideMenuLinks', App.SideMenuLinksView);
The corresponding view can use a template similar to yours by giving a templateName
App.SideMenuLinksView = Ember.View.extend({
templateName: 'sideMenuLinksTemplate'
});
You template would be something like,
<script type='text/x-handlebars' data-template-name='sideMenuLinksTemplate'>
{{#each link in view.links}}
<li class="navigation page">
{{#linkFor parentView.routeVariable link.linkToRouteContext}}
{{{link.iconTag}}}<i class="icon-right-open"></i>{{link.linkText}}</a>
{{/linkFor}}
</li>
{{/each}}
</script>
The default Ember linkTo is static, In that you cannot pass the named route from a variable. You will need a dynamic linkTo helper like the linkFor that looks up the variable path before
applying linkTo internally.
Ember.Handlebars.registerHelper('linkFor', function(path, params, options) {
var view = options.data.view;
var name = view.get(path);
var args = [name, params, options];
return Ember.Handlebars.helpers.linkTo.apply(this, args);
});
Finally you can use this helper like below. The links property will be bound to the content of the controller in this example.
<script type='text/x-handlebars' data-template-name='application'>
{{sideMenuLinks links=content routeVariable='page'}}
</script>
Here's a working jsbin.