This is part of my component's template:
{{#each displayResults}}
<li {{action addSelection this}} {{bindAttr class=\":result active\"}}>
{{#if controller.template}}
{{yield}}
{{else}}
<span class=\"result-name\">{{displayHelper controller.searchPath}}</span>
{{/if}}
<\/li>
{{/each}}
I want the user to be able to customise the html that is used to display the results.
The problem is that the {{yield}} is called while in an {{#each}} helper and if the component is declared like this:
{{#auto-suggest source=controller.employees destination=controller.chosenEmployees}}
<span class=\"result-name\"><img src="img/small_avatar.png"/>{{fullName}}</span>
{{/auto-suggest}}
Then the block between the {{#auto-suggest}} does not have the context of the {{#each}} helper in the component.
Is there anything I can do or is this just the way it is?
This is part of my component's template:
{{#each displayResults}}
<li {{action addSelection this}} {{bindAttr class=\":result active\"}}>
{{#if controller.template}}
{{yield}}
{{else}}
<span class=\"result-name\">{{displayHelper controller.searchPath}}</span>
{{/if}}
<\/li>
{{/each}}
I want the user to be able to customise the html that is used to display the results.
The problem is that the {{yield}} is called while in an {{#each}} helper and if the component is declared like this:
{{#auto-suggest source=controller.employees destination=controller.chosenEmployees}}
<span class=\"result-name\"><img src="img/small_avatar.png"/>{{fullName}}</span>
{{/auto-suggest}}
Then the block between the {{#auto-suggest}} does not have the context of the {{#each}} helper in the component.
Is there anything I can do or is this just the way it is?
Share Improve this question edited Dec 24, 2016 at 13:59 Marcio Junior 19.1k4 gold badges46 silver badges47 bronze badges asked Nov 20, 2013 at 22:22 dagda1dagda1 28.8k67 gold badges255 silver badges477 bronze badges2 Answers
Reset to default 18UPDATE
Now that ember 1.10 has landed, a new syntax was introduced called block params. So there is no need to override the _yield
method. For instance inside of your component's template you do:
<ul>
{{#each item in source}}
<li>
{{! the component is being used in the block form, so we yield}}
{{#if template.blockParams}}
{{yield item}}
{{! no block so just display the item}}
{{else}}
{{item}}
{{/if}}
</li>
{{/each}}
</ul>
And then when using the component you get the params passed to {{yield}}
using as |var|
{{! no block, the component will just display the item}}
{{auto-suggest source=model as |item|}}
{{! in the block form our custom html will be used for each item}}
{{#auto-suggest source=model as |item|}}
<h1>{{item}}</h1>
{{/auto-suggest}}
Simple live example
Of course, you can yield any variables using {{yield name age occupation hobbies}}
and catch them in the component with:
{{#x-foo user=model as |name age occupation hobbies|}}
Hi my name is {{name}}, I am {{age}} years old. Major of the times I am {{occupation}}, but also love to {{hobbies}}.
{{/x-foo}}
FOR OLD VERSIONS
You can override the default _yield
method, of Ember.Component
, and change the context: get(parentView, 'context')
to context: get(view, 'context')
.
App.AutoSuggestComponent = Ember.Component.extend({
_yield: function(context, options) {
var get = Ember.get,
view = options.data.view,
parentView = this._parentView,
template = get(this, 'template');
if (template) {
Ember.assert("A Component must have a parent view in order to yield.", parentView);
view.appendChild(Ember.View, {
isVirtual: true,
tagName: '',
_contextView: parentView,
template: template,
context: get(view, 'context'), // the default is get(parentView, 'context'),
controller: get(parentView, 'controller'),
templateData: { keywords: parentView.cloneKeywords() }
});
}
}
});
Based on Marcio Junior's answer, you can augment _yield
without recreating the entire function. Though instead of transparently changing the behaviour of _yield
, i would recommend adding an option to your component. E.g.,
{{#whatever-component useComponentContext=true}}
And in your component's JavaScript file:
// duck-punch _yield to use the current view
_yield: function(content, options) {
var oldParentView = this._parentView;
if (this.get('useComponentContext')) {
// temporarily set _parentView to the view we want
this.set('_parentView', options.data.view);
// expose the original context via `_parent`
options.data.view.get('context').set('_parent', oldParentView.get('context'));
}
// call the built-in _yield like normal
var result = this._super.apply(this, arguments);
// restore _parentView
this.set('_parentView', oldParentView);
return result;
}
With this, something like
{{!-- whatever-component.hbs --}}
{{#each something in somethings}}
<div {{bind-attr title=something.id}}>
{{#with something}}
{{yield}}
{{/with}}
</div>
{{/each}}
{{!-- some-view.hbs --}}
{{#whatever-component.hbs}}
<h1>{{id}}</h1>
<p>{{someField}}</p>
<div>{{_parent.theContextTheComponentIsBeingRenderedIn}}</div>
{{/whatever-component.hbs}}
will work as expected