Ember.Component (block form): more than one outlet {{yield}}

允我心安 提交于 2019-11-30 06:26:01

Since it is not possible to have two {{yield}} helpers within one component (how would the component know where one {{yield}}'s markup stops and the next one begins?) you may be able to approach this problem from a different direction.

Consider the pattern of nested components. Browsers do this already with great success. Take, for example, the <ul> and <li> components. A <ul> wants to take many bits of markup and render each one like a member of a list. In order to accomplish this, it forces you to separate your itemized markup into <li> tags. There are many other examples of this. <table>, <tbody>, <tr>, <td> is another good case.

I think you may have stumbled upon a case where you can implement this pattern. For example:

{{#sfa-item}}
  {{#first-thing}}
     ... some markup
  {{/first-thing}}

  {{#second-thing}}
    ... some other markup
  {{/second-thing}}
{{/sfa-item}}

Obviously first-thing and second-thing are terrible names for your specialized components that represent the things you'd want to wrap with your first and second templates. You get the idea.

Do be careful since the nested components won't have access to properties within the outer component. You'll have to bind values with both outer and inner components if they are needed in both.

This blog post describes the most elegant solution for Ember 1.10+: https://coderwall.com/p/qkk2zq/components-with-structured-markup-in-ember-js-v1-10

In your component you pass yield names into {{yield}}s:

<header>
  {{yield "header"}}
</header>

<div class="body">
  {{yield "body"}}
</div>

<footer>
  {{yield "footer"}}
</footer>

When you invoke your component, you accept the yield name as a block param... and use an esleif chain!

{{#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 is a subexpression helper from the must-have ember-truth-helpers addon.

PPS Relevant RFC: proposal, discussion.

I added an extension to CoreView to handle this (at the end of this post). The extension works by highjacking the _renderToBuffer method and

  1. registering temporary Handlebars helpers to capture 'placeholder content' from the inner content of the component instance,
  2. rendering the component to a temporary, discarded buffer (essentially calling yield on the component's behalf),
  3. reregistering Handlebars helpers to inject the captured 'placeholder content',
  4. rendering the component's template like normal,
  5. cleaning up (restoring the Handlebars helpers to their previous state).

For the component's template, use placeholders, like this

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

In the component's definition, include a 'placeholders' property that identifies the placeholders that will be used:

App.BootstrapDialogComponent = Ember.Component.extend({
   placeholders: [ 'header', 'body', 'footer' ],
   ...
});

Then, when using the component, use the placeholder name, like this

{{#bootstrap-dialog}}
  {{#header}}
    Add Product
  {{/header}}
  {{#body}}
    ...form...
  {{/body}}
  {{#footer}}
    <button>Save</button>
  {{/footer}}
{{/bootstrap-dialog}}

Here's the code that shoves in the extension:

(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);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!