Ember.Component(блочная форма): более одного выхода {{yield}}
Я вижу, что в ember есть очень хороший механизм для оборачивания содержимого в компоненте с использованием механизма {{yield}}
описанного здесь.
Итак, чтобы использовать пример в документации, у меня может быть шаблон компонента blog-post
определенный так:
<script type="text/x-handlebars" id="components/blog-post">
<h1>{{title}}</h1>
<div class="body">{{yield}}</div>
</script>
Затем я могу встроить blog-post
в blog-post
в любой другой шаблон, используя форму:
{{#blog-post title=title}}
<p class="author">by {{author}}</p>
{{body}}
{{/blog-post}}
У меня вопрос, могу ли я указать два разных {{yield}}
в шаблоне компонентов?
Нечто подобное возможно через Named Outlets в Ember.Route#renderTemplate
примерно так:
Рули:
<div class="toolbar">{{outlet toolbar}}</div>
<div class="sidebar">{{outlet sidebar}}</div>
JavaScript:
App.PostsRoute = Ember.Route.extend({
renderTemplate: function() {
this.render({ outlet: 'sidebar' });
}
});
Я не уверен, что могу использовать этот путь для компонента, который не будет знать, какой шаблон маршрута будет его отображать.
РЕДАКТИРОВАТЬ 1:
Для ясности я пытаюсь реализовать Android Swipe for Action Pattern в качестве компонента Ember.
Итак, я бы хотел, чтобы пользователи этого компонента могли указывать два разных шаблона:
- Шаблон для обычного элемента списка и
- Шаблон для действий, которые обнаруживаются при обнаружении удара по (1).
Я хочу превратить это в компонент, потому что довольно много javascript используется для обработки событий касания (начало/перемещение/конец), в то же время управляя плавной прокруткой списка на основе касания. Пользователи будут предоставлять два шаблона, а этот компонент будет управлять обработкой сенсорных событий и необходимой анимацией.
Мне удалось заставить компонент работать в форме блока, где содержимое блока обрабатывается как (1). Второй шаблон (2) указывается через параметр (actionPartial
ниже), который является именем частичного шаблона для действий:
Шаблонный руль: sfa-item.handlebars
<div {{bind-attr class=":sfa-item-actions shouldRevealActions:show" }}>
{{partial actionPartial}}
</div>
<div {{bind-attr class=":sfa-item-details isDragging:dragging shouldRevealActions:moveout"}}>
{{yield}}
</div>
Шаблон вызова руля:
{{#each response in controller}}
<div class="list-group-item sf-mr-item">
{{#sfa-item actionPartial="mr-item-action"}}
<h5>{{response.name}}</h5>
{{/sfa-item}}
</div>
{{/each}}
Где руль mr-item-action
определен так:
MR-вещь-action.handlebars:
<div class="sf-mr-item-action">
<button class="btn btn-lg btn-primary" {{action 'sfaClickedAction'}}>Edit</button>
<button class="btn btn-lg btn-primary">Delete</button>
</div>
Проблема в том, что действия от части, предоставленной пользователем, sfaClickedAction
выше, не всплывают из компонента. Факт, который упоминается в документах в пункте 4.
Итак, теперь я не знаю, как пользователь мог зафиксировать действия, которые он определил в предоставленном шаблоне действий. Компонент не может перехватить эти действия, потому что он также не знает о них.
РЕДАКТИРОВАТЬ 2
Я задал следующий вопрос здесь
Ответы
Ответ 1
Поскольку в одном компоненте невозможно иметь два помощника {{yield}}
(как компонент узнает, где разметка {{yield}}
останавливается, а следующая начинается?), вы можете подойти к этой проблеме с другого направления.
Рассмотрим шаблон вложенных компонентов. Браузеры делают это уже с большим успехом. Возьмите, например, компоненты <ul>
и <li>
. A <ul>
хочет взять много бит разметки и сделать каждый из них похожим на список. Чтобы добиться этого, он заставит вас разделить вашу разметку по разметке на теги <li>
. Есть много других примеров этого. <table>
, <tbody>
, <tr>
, <td>
- еще один хороший случай.
Я думаю, вы, возможно, наткнулись на случай, когда вы можете реализовать этот шаблон. Например:
{{#sfa-item}}
{{#first-thing}}
... some markup
{{/first-thing}}
{{#second-thing}}
... some other markup
{{/second-thing}}
{{/sfa-item}}
Очевидно, first-thing
и second-thing
являются страшными именами для ваших специализированных компонентов, которые представляют вещи, которые вы хотите обернуть с помощью первого и второго шаблонов. Вы получаете идею.
Будьте осторожны, поскольку вложенные компоненты не будут иметь доступа к свойствам внешнего компонента. Вам придется привязывать значения как к внешним, так и к внутренним компонентам, если они необходимы в обоих.
Ответ 2
В этом сообщении блога описано наиболее элегантное решение для Ember 1.10+: https://coderwall.com/p/qkk2zq/components-with-structured-markup-in-ember-js-v1-10
В вашем компоненте вы передаете имена результатов в {{yield}}
s:
<header>
{{yield "header"}}
</header>
<div class="body">
{{yield "body"}}
</div>
<footer>
{{yield "footer"}}
</footer>
Когда вы вызываете свой компонент, вы принимаете имя урока в качестве параметра блока... и используете цепочку esleif!
{{#my-comp as |section|}}
{{#if (eq section "header")}}
My header
{{else if (eq section "body")}}
My body
{{else if (eq section "footer")}}
My footer
{{/if}}
{{/my-comp}}
PS eq
является вспомогательным подвыражением из добавочного дополнения ember-truth-helpers.
PPS Соответствующий RFC: предложение, обсуждение.
Ответ 3
Я добавил расширение для CoreView для обработки этого (в конце этого сообщения). Расширение работает путем высокоуровневого метода _renderToBuffer и
- регистрация временных помощников Handlebars для захвата "содержимого заполнителя" из внутреннего содержимого экземпляра компонента,
- рендеринг компонента во временный, отброшенный буфер (по существу вызывающий доход от имени компонента),
- reregistering Handlebars helpers, чтобы ввести захваченный контент-заполнитель,
- отображение шаблона компонента как обычного,
- очистка (восстановление помощников Handlebars до их предыдущего состояния).
Для шаблона компонента используйте заполнители, например
<div class='modal-dialog'>
<div class='modal-content'>
<div class='modal-header'>
{{header}}
</div>
<div class='modal-body'>
{{body}}
</div>
<div class='modal-footer'>
{{footer}}
</div>
</div>
</div>
В определении компонента включите свойство "placeholders", которое идентифицирует заполнители, которые будут использоваться:
App.BootstrapDialogComponent = Ember.Component.extend({
placeholders: [ 'header', 'body', 'footer' ],
...
});
Затем, используя компонент, используйте имя-заполнителя, например
{{#bootstrap-dialog}}
{{#header}}
Add Product
{{/header}}
{{#body}}
...form...
{{/body}}
{{#footer}}
<button>Save</button>
{{/footer}}
{{/bootstrap-dialog}}
Здесь код, который загружается в расширение:
(function() {
var _renderToBufferOriginal = Ember.CoreView.create()._renderToBuffer;
Ember.CoreView.reopen({
_renderToBuffer: function(buffer, bufferOperation) {
this.transitionTo('inBuffer', false);
var placeholders = { }
if (this.placeholders) {
this.placeholders.map(function(n) {
placeholders[n] = {
saved: Ember.Handlebars.helpers[n],
buffer: null,
}
Ember.Handlebars.helpers[n] = function(options) {
var tmp = placeholders[n].buffer = placeholders[n].buffer || Ember.RenderBuffer();
var saved = options.data.buffer;
options.data.buffer = tmp;
options.fn(options.contexts[0], options);
options.data.buffer = saved;
};
});
Ember.Handlebars.helpers['yield'].call(this, {
hash: { },
contexts: [ this.get('context') ],
types: [ "ID" ],
hashContexts: { },
hashTypes: { },
data: {
view: this,
buffer: Ember.RenderBuffer(),
isRenderData: true,
keywords: this.cloneKeywords(),
insideGroup: this.get('templateData.insideGroup'),
}
});
this.placeholders.map(function(n) {
Ember.Handlebars.helpers[n] = function(options) {
var str = ((placeholders[n].buffer)? placeholders[n].buffer.innerString(): "");
options.data.buffer.push(str);
};
});
}
var result = this._renderToBufferOriginal.apply(this, arguments);
if (this.placeholders) {
this.placeholders.map(function(n) {
Ember.Handlebars.helpers[n] = placeholders[n].saved;
});
}
return result;
},
_renderToBufferOriginal: _renderToBufferOriginal,
});
}).call(this);