Accessing component scope within template block - ember.js

if I have a component called my-scope which is defined only as:
export default Ember.Component.extend({
foo: 'bar'
})
and then I use the component in a template, like so:
{{#my-scope}}
{{foo}}
{{/my-scope}}
How can I reach the scope of the surrounding block component? I have tried:
{{foo}}
{{component.foo}}
{{view.foo}}
Maybe this is a case where I need to use a view? Hoping not but maybe that is the case.
note: my use-case is far more complex but let's just say that normal binding approach to components {{#my-scope foo=outsideFoo}} ... {{/my-scope}} is not a workable example

Without knowing your specific use-case, it's hard to know if the following answer will work for you, but one thing is for sure - it is very much compliant with Ember 2.0 "data down, actions up" philosophy (see this excellent piece by Sam Selikoff).
You can create a component that sends an action up with the data that you want available in your surrounding context as follows:
App.XTestComponent = Ember.Component.extend({
foo: 'bar',
didInsertElement: function(){
this.sendAction('action', this.get('foo'));
}
});
Then, in your surrounding context (controller), you can catch the action and set the appropriate data:
App.ApplicationController = Ember.Controller.extend({
foo: 'controller',
actions: {
setFoo: function(param){
this.set('foo', param);
}
}
});
Now, you can use your foo variable in the surrounding context the way you showed in the example:
<script type="text/x-handlebars">
{{#x-test action='setFoo'}} Hmm {{ foo }} {{/x-test}}
</script>
<script type="text/x-handlebars" id='components/x-test'>
<h2>Testing</h2>
{{ yield }}
</script>
See a working example here
(In theory, you can do this using an observer and not just from inside didInsertElement, but I have not tested it.)

You can use _view.parentView to access the value of foo property inside the component. (Because there is an underscore in _view, to me that means that it's not public API, so they might get rid of it in the future - but it's around for now :))
Something like the following:
{{#x-test}} Inside c1: {{ _view.parentView.foo.name }} {{/x-test}}
Working demo here

Related

How do I specify a mix of an expression and a string as a class for an ember component?

I want to do something like this
{{#my-custom-component class="some-class-name {{dynamicProperty}}" }}
But this is literally rendering some-class-name {{dynamicProperty}} as the classname, rather than the value it represents.
You're already inside an expression, so you'll want to leave off the {{ - and also the quotes in this case - because it isn't a string. : )
<div class='{{exampleClass}}'>message in template</div>
{{!-- vs --}}
{{#example-component class=exampleClass}}message in component{{/example-component}}
This is your components 'controller' basically:
export default Ember.Component.extend({
tagName: 'section',
classNames: ['class-from-component-controller'],
});
The scope of your template (of the same name) uses this controller as it's 'memory' in a way. - so, you can use the built in property 'classNames' array - in the controller / or the template.
{{#example-component class='strong-voice' classNames=exampleClass}}
message in component
{{/example-component}}
twiddle example
Use the concat helper:
{{#my-custom-component class=(concat 'some-class-name' dynamicProperty) }}

ember application template classname

I'm having a similar problem to this one:
Setting tagName on application template in ember js
While I agree that falling back to a legacy view addon can't be the way to go, here too my bootstrap-based CSS is broken by the enclosing div (the height being not set, to be precise).
Now a different way to achieve what I need is to set the enclosing div's classNames property (if it exists), like it can be done with a component:
export default Ember.Component.extend({
classNames: ['container']
});
Thus I could apply height:100%, and everything would be fine.
Update:
The problem is not the styling of the enclosing div of a component, but the way the main application template behaves. Let me clarify:
application.hbs:
{{outlet}}
Therein is rendered a route's template, e.g. map.hbs:
{{#tab-navigation-container}}
{{top-nav}}
{{tab-contentpane model=model}}
{{tab-navigation map=true}}
{{/tab-navigation-container}}
Now, components/tab-navigation-container.js transforms the enclosing div to include the container CSS class:
import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['container']
});
However, the rendered HTML looks like this:
So, seemingly application.hbs puts another div around the component, and I'm looking for a way to either
remove it (which can only be achieved by a legacy view addon, as explained in the link above) or
apply a className to it.
Can it be done? Thanks!
Okay, this is more of an ugly hack than an actual solution, but for the moment it does the trick. I just fell back to ordinary jQuery DOM manipulation while overwriting the component's didInsertElement event:
export default Ember.Component.extend({
setupFunc: function () {
$('#tab-navigation-container').unwrap();
}.on('didInsertElement')
});
where tab-navigation-container is the component's ID.
You should set tagName and classNames to be your outer most element--this will keep consistent styling. For example, say originally you had:
<section class="container col-md-6">
<div class="col-sm-12 test">
<p>some content...</p>
...
</div>
</section>
You would create your component like this:
export default Ember.Component.extend({
tagName: 'section',
classNames: ['container', 'col-md-6']
});
Then your template can exclude the outer section wrapper
<div class="col-sm-12 test">
<p>some content...</p>
...
</div>
There's really no reason why your CSS should be broken by using components. You can set all the same classes and tags that you were using before and CSS should render just the same.
Let me know if I misunderstood your question.
In Ember 2.7 and below, you can control the top-level tag (even not rendering it), and its CSS classes by modifying your Ember Application as follows:
// app/app.js
App = Ember.Application.extend({
ApplicationView: Ember.Component.extend({
classNames: ['my-custom-classname'],
// Or to omit the tag entirely:
tagName: '',
}),
// ...
});
As seen in this discussion.
This does not require any hacks or a legacy plugin.

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.

EmberJS Template concatenate

How can I can concatenate strings( or how to add classes ) on templates on EmberJs?
ex.
<script type="text/x-handlebars">
// This div I want to add a class go, Is this the right way to do it?
<div class="fly {{isGo}}">Fly now</div>
// Or it's something like this?
<div class="fly "{{isGo}} >Fly now</div>
</script>
bind-attr used to be a good way of working around a limitation within Ember's rendering. Now with HTMLbars Ember has recommend that we move away from bind-attr as we have more powerful methods.
Ember 1.13 deprecated bind-attr in favor of the new syntax.
http://emberjs.com/deprecations/v1.x/#toc_bind-attr
Working example of the two proposed methods can be seen in action on ember twiddle ,here:
https://ember-twiddle.com/38f69f01d2fd994af3b0965f10882005?openFiles=templates.application.hbs%2C
Method 1
If you want to do the combination inside your handlebars template you could do something like:
<div class={{concat "fly " isGo}}>Fly now</div>
Method 2
otherwise use a computed property like:
flyingClass: Ember.computed('isGo', function() {
// return a string with 'fly' and the value of
// isGo. Will be updated if isGo changes.
// Array values are created with space delimited by
// ['className', 'anotherClassName', 'lastClastName'].join(' ');
// => "className anotherClassName lastClassName"
let going = this.get('isGo') ? 'going' : ''
return ['fly', going].join(' ');
})
and then in your handlebars template:
<div class={{flyingClass}}>Fly now</div>
The main difference between the two methods depends on how you want your separation of concerns. Right now it might be easier to just do Method 1, but as conditions get more complicated you could hide more of the work in the computed property.
There is a complete discussion of this in the Ember guide: http://emberjs.com/guides/templates/binding-element-class-names/
But you'd do it like this:
<div {{bind-attr class="isGo"}}>Fly now</div>
And in your controller:
App.MyController = Ember.ObjectController.extend({
flightIsAGo: true,
isGo: function() {
return "fly"+this.get('flightIsAGo') ? ' isGo' : '';
}.property('flightIsAGo')
}

Ember: how to use i18n translations in controller code?

I am using Ember i18n in my app. I also want to use the translation strings in the controllers (in most cases in an alert or confirm message). How can this be done ?
See http://jsfiddle.net/cyclomarc/36VS3/2/
Clicking on the button should alert "info" and not "T1005" ...
<script type="text/x-handlebars">
{{t T1005}}<br>
<button {{action 'clickMe' content}}>{{t T1005}} - Click me</button>
</script>
CLDR.defaultLanguage = 'en';
App.ApplicationController = Ember.Controller.extend({
clickMe: function(){
alert('T1005');
}
})
I know that a possible workaround is to no longer use alert and confirm and replace them by for example the bootstrap alternatives. However, I could imagine that in certain cases you will want to do something with the strings in Javascript (e.g. update a certain label via jQuery or so).
Any ideas on how to use the i18n strings in the controllers is helpful. Using an i18n library is only usefull if all aspects of the application can be translated ...
Just found the solution. Just access the string via Ember.I18n.t("T1005");
JSFiddle updated: http://jsfiddle.net/cyclomarc/36VS3/7/
Might be late here, but what about using the Em.I18n.TranslateableProperties mixin as documented here ?
You could do something like :
App.ApplicationController = Ember.Controller.extend(Em.I18n.translateableProperties, {
messageTranslation: 'T1005',
clickMe: function(){
alert(this.get('message'));
}
});
In the template, {{message}} will also hold the translation.
The solution that works to me is the following (using Ember I18n):
App.ApplicationController = Ember.Controller.extend(Em.I18n.translateableProperties, {
messageTranslation: 'T001',
showMessage: function(){
alert(this.get('message'));
}
});
The answer from cyclomarc didn't work for me (it's from 2013, which might be related), but it pointed me in the right direction:
this.container.lookup('service:i18n').t('my.translation.id')