Подтвердить что ты не робот

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. Шаблон для обычного элемента списка и
  2. Шаблон для действий, которые обнаруживаются при обнаружении удара по (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


Я задал следующий вопрос здесь

4b9b3361

Ответ 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);