title: how to access parent component scope from a child components scope in ember?
body: <p>I'm curious if this is even possible in ember. This is an easy thing to do in angular ( plunkr: <a href="http://plnkr.co/edit/O2e0ukyXdKMs4FcgKGmX?p=preview" rel="noreferrer">http://plnkr.co/edit/O2e0ukyXdKMs4FcgKGmX?p=preview</a> ):</p>
  
  <p>The goal is to make an easy to use, generic, reusable accordion api for api consumers.</p>
  
  <p>The api I want the caller to be able to use is this (just like the angular api):</p>
  
  <pre><code>{{#ember-accordion listOfAccordionPaneObjects=model}}
  
    {{#ember-accordion-heading}}
       heading template html {{accordionPaneObject.firstName}}
    {{/ember-accordion-heading}}
  
    {{#ember-accordion-body}}
       this is the accordion body  {{accordionPaneObject.lastName}}
    {{/ember-accordion-body}}
  
  {{/ember-accordion}}
  </code></pre>
  
  <p>Here is a working example I wrote using angular:</p>
  
  <pre><code>&lt;!doctype html&gt;
  &lt;html ng-app="angular-accordion"&gt;
  &lt;head&gt;
      &lt;style&gt;
          .angular-accordion-header {
              background-color: #999;
              color: #ffffff;
              padding: 10px;
              margin: 0;
              line-height: 14px;
              -webkit-border-top-left-radius: 5px;
              -webkit-border-top-right-radius: 5px;
              -moz-border-radius-topleft: 5px;
              -moz-border-radius-topright: 5px;
              border-top-left-radius: 5px;
              border-top-right-radius: 5px;
              cursor: pointer;
              text-decoration: none;
              font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
              font-size: 14px;
          }
  
          .angular-accordion-container {
              height: 100%;
              width: 100%;
          }
  
          .angular-accordion-pane {
              padding: 2px;
          }
  
          .angularaccordionheaderselected {
              background-color: #bbb;
              color: #333;
              font-weight: bold;
          }
  
          .angular-accordion-header:hover {
              text-decoration: underline !important;
          }
  
          .angularaccordionheaderselected:hover {
              text-decoration: underline !important;
          }
  
          .angular-accordion-pane-content {
              padding: 5px;
              overflow-y: auto;
              border-left: 1px solid #bbb;
              border-right: 1px solid #bbb;
              border-bottom: 1px solid #bbb;
              -webkit-border-bottom-left-radius: 5px;
              -webkit-border-bottom-right-radius: 5px;
              -moz-border-radius-bottomleft: 5px;
              -moz-border-radius-bottomright: 5px;
              border-bottom-left-radius: 5px;
              border-bottom-right-radius: 5px;
          }
  
          .angulardisabledpane {
              opacity: .2;
          }
      &lt;/style&gt;
  &lt;/head&gt;
  &lt;body style="margin: 0;"&gt;
  
  
  &lt;div style="height: 90%; width: 100%; margin: 0;" ng-controller="outerController"&gt;
  
      &lt;angular-accordion list-of-accordion-pane-objects="outerControllerData"&gt;
          &lt;pane&gt;
              &lt;pane-header&gt;Header {{accordionPaneObject}}&lt;/pane-header&gt;
              &lt;pane-content&gt;Content {{accordionPaneObject}}&lt;/pane-content&gt;
          &lt;/pane&gt;
      &lt;/angular-accordion&gt;
  
  &lt;/div&gt;
  
      &lt;script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js"&gt;&lt;/script&gt;
      &lt;script&gt;
          angular.module('angular-accordion', [])
                  .directive('angularAccordion', function() {
                      var template = '';
  
                      return {
                          restrict: 'E',
                          transclude: true,
                          replace: true,
                          template: '&lt;div&gt;' +
                                          '&lt;div ng-transclude class="angular-accordion-container" ng-repeat="accordionPaneObject in listOfAccordionPaneObjects"&gt;&lt;/div&gt;' +
                                    '&lt;/div&gt;',
                          controller: ['$scope', function($scope) {
                              var panes = [];
  
                              this.addPane = function(pane) {
                                  panes.push(pane);
                              };
                          }],
                          scope: {
                              listOfAccordionPaneObjects: '='
                          }
                      };
                  })
                  .directive('pane', function() {
                      return {
                          restrict: 'E',
                          transclude: true,
                          replace: true,
                          template: '&lt;div ng-transclude class="angular-accordion-pane"&gt;&lt;/div&gt;'
                      };
                  })
                  .directive('paneHeader', function() {
                      return {
                          restrict: 'E',
                          require: '^angularAccordion',
                          transclude: true,
                          replace: true,
                          link: function(scope, iElement, iAttrs, controller) {
                              controller.addPane(scope);
  
                              scope.toggle = function() {
                                  scope.expanded = !scope.expanded;
                              };
                          },
                          template: '&lt;div ng-transclude class="angular-accordion-header" ng-click="toggle()"&gt;&lt;/div&gt;'
                      };
                  })
                  .directive('paneContent', function() {
                      return {
                          restrict: 'EA',
                          require: '^paneHeader',
                          transclude: true,
                          replace: true,
                          template: '&lt;div ng-transclude class="angular-accordion-pane-content" ng-show="expanded"&gt;&lt;/div&gt;'
                      };
                  })
                  .controller('outerController', ['$scope', function($scope) {
                      $scope.outerControllerData = [1, 2, 3];
                  }]);
      &lt;/script&gt;
  &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p>here's where I'm stuck doing the same with ember:</p>
  
  <p>index.html</p>
  
  <pre><code>&lt;!DOCTYPE html&gt;
  &lt;html&gt;
      &lt;body&gt;
          &lt;script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.9/require.js" data-main="main.js"&gt;&lt;/script&gt;
      &lt;/body&gt;
  &lt;/html&gt;
  </code></pre>
  
  <p>main.js</p>
  
  <pre><code>require.config({
      paths: {
          'ember': 'bower_components/ember/ember',
          'handlebars': 'bower_components/handlebars/handlebars',
          'jquery': 'bower_components/jquery/jquery',
          'text': 'bower_components/requirejs-text/text'
      },
      shim: {
          ember: {
              deps: ['jquery', 'handlebars'],
              exports: 'Ember'
          }
      }
  });
  
  define(function(require) {
      var Ember = require('ember'),
          EmberAccordionComponent = require('src/EmberAccordionComponent'),
          EmberAccordionTemplate = require('text!templates/ember-accordion.hbs'),
          EmberAccordionHeaderTemplate = require('text!templates/ember-accordion-header.hbs'),
          EmberAccordionBodyTemplate = require('text!templates/ember-accordion-body.hbs'),
          ApplicationTemplate = require('text!templates/application.hbs'),
          IndexTemplate = require('text!templates/index.hbs');
  
      var App = Ember.Application.create({
          LOG_STACKTRACE_ON_DEPRECATION : true,
          LOG_BINDINGS                  : true,
          LOG_TRANSITIONS               : true,
          LOG_TRANSITIONS_INTERNAL      : true,
          LOG_VIEW_LOOKUPS              : true,
          LOG_ACTIVE_GENERATION         : true
      });
  
      Ember.TEMPLATES = {};
      Ember.TEMPLATES['application'] = Ember.Handlebars.compile(ApplicationTemplate);
      Ember.TEMPLATES['index'] = Ember.Handlebars.compile(IndexTemplate);
      Ember.TEMPLATES['components/ember-accordion'] = Ember.Handlebars.compile(EmberAccordionTemplate);
      Ember.TEMPLATES['components/ember-accordion-header'] = Ember.Handlebars.compile(EmberAccordionHeaderTemplate);
      Ember.TEMPLATES['components/ember-accordion-body'] = Ember.Handlebars.compile(EmberAccordionBodyTemplate);
  
      App.EmberAccordionComponent = EmberAccordionComponent;
  
      App.IndexRoute = Ember.Route.extend({
          model: function() {
              return [
                  {
                      name: 'Bob'
                  },
                  {
                      name: 'Jill'
                  }]
          }
      })
  });
  </code></pre>
  
  <p>EmberAccordionComponent.js</p>
  
  <pre><code>define(function(require) {
      require('ember');
  
      var EmberAccordionComponent = Ember.Component.extend({});
  
      return EmberAccordionComponent;
  });
  </code></pre>
  
  <p>application.hbs</p>
  
  <pre><code>{{outlet}}
  </code></pre>
  
  <p>ember-accordion-header.hbs</p>
  
  <pre><code>&lt;div style="color: blue;"&gt;
      {{yield}}
  &lt;/div&gt;
  </code></pre>
  
  <p>ember-accordion-body.hbs</p>
  
  <pre><code>&lt;div style="color: green;"&gt;
      {{yield}}
  &lt;/div&gt;
  </code></pre>
  
  <p>index.hbs</p>
  
  <pre><code>{{#ember-accordion listOfAccordionPaneObjects=model}}
      {{#ember-accordion-header}}
          {{log this.constructor}}
          {{log this}}
          Header {{accordionPaneObject.name}}
      {{/ember-accordion-header}}
      {{#ember-accordion-body}}
          Body {{accordionPaneObject.name}}
      {{/ember-accordion-body}}
  {{/ember-accordion}}
  </code></pre>
  
  <p>ember-accordion.hbs</p>
  
  <pre><code>{{#each accordionPaneObject in listOfAccordionPaneObjects}}
      {{yield}}
  {{/each}}
  </code></pre>
  
  <p>--</p>
  
  <p>This is tricky to debug.  So putting in the:</p>
  
  <pre><code>{{log this.constructor}}
  </code></pre>
  
  <p>and the:</p>
  
  <pre><code>{{log this}}
  </code></pre>
  
  <p>into the:</p>
  
  <pre><code>{{#ember-accordion-header}}
  </code></pre>
  
  <p>outputs the following:</p>
  
  <ul>
  <li>Class.model = undefined   (why?)</li>
  <li>Ember.ArrayController</li>
  </ul>
  
  <p>I've tried overriding the private _yield method of Ember.Component as suggested by this article ( <a href="http://www.thesoftwaresimpleton.com/blog/2013/11/21/component-block/" rel="noreferrer">http://www.thesoftwaresimpleton.com/blog/2013/11/21/component-block/</a> ):</p>
  
  <pre><code>var EmberAccordionHeaderComponent = 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(view, 'controller'), // the default is get(parentView, 'context'),
                  templateData: { keywords: parentView.cloneKeywords() }
              });
          }
      }
  });
  </code></pre>
  
  <p>but when I do this I still don't have access to accordionPaneObject in my child component scope, and my {{log this.constructor}} now points to: .EmberAccordionHeaderComponent</p>
  
  <p>So it looks like I'm getting somewhere, I just need to go one more level up.</p>
  
  <p>When I try that using this code in EmberAccordionHeaderComponent.js:</p>
  
  <pre><code>var EmberAccordionHeaderComponent = Ember.Component.extend({
      _yield: function(context, options) {
          var get = Ember.get,
              view = options.data.view,
              parentView = this._parentView,
              grandParentView = this._parentView._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(grandParentView, 'context'), // the default is get(parentView, 'context'),
                  controller: get(grandParentView, 'controller'), // the default is get(parentView, 'context'),
                  templateData: { keywords: parentView.cloneKeywords() }
              });
          }
      }
  });
  </code></pre>
  
  <p>I still don't access to accordionPaneObject in, but now I see {{log this.constructor}} outputting .EmberAccordionComponent.  So it appears I'm in the right scope, but the data still doesn't bind.</p>
  
  <p>Interestingly enough, if I use any of these variations of reassigning context and controller in my overridden _yield, I can access the data I am after in the console using:</p>
  
  <pre><code>this._parentView._context.content
  </code></pre>