2013-11-23 55 views
4

我很好奇,如果这是甚至可能在烬。这是一个容易做的角度(plunkr:http://plnkr.co/edit/O2e0ukyXdKMs4FcgKGmX?p=preview):如何从一个子组件作用域中访问父组件作用域?

目标是为api消费者制作一个易于使用的,通用的,可重复使用的手风琴api。

我想要的主叫方能够使用该API是这样的(就像角API):

{{#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}} 

这里是一个工作的例子,我采用了棱角分明写道:

<!doctype html> 
<html ng-app="angular-accordion"> 
<head> 
    <style> 
     .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; 
     } 
    </style> 
</head> 
<body style="margin: 0;"> 


<div style="height: 90%; width: 100%; margin: 0;" ng-controller="outerController"> 

    <angular-accordion list-of-accordion-pane-objects="outerControllerData"> 
     <pane> 
      <pane-header>Header {{accordionPaneObject}}</pane-header> 
      <pane-content>Content {{accordionPaneObject}}</pane-content> 
     </pane> 
    </angular-accordion> 

</div> 

    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.js"></script> 
    <script> 
     angular.module('angular-accordion', []) 
       .directive('angularAccordion', function() { 
        var template = ''; 

        return { 
         restrict: 'E', 
         transclude: true, 
         replace: true, 
         template: '<div>' + 
             '<div ng-transclude class="angular-accordion-container" ng-repeat="accordionPaneObject in listOfAccordionPaneObjects"></div>' + 
            '</div>', 
         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: '<div ng-transclude class="angular-accordion-pane"></div>' 
        }; 
       }) 
       .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: '<div ng-transclude class="angular-accordion-header" ng-click="toggle()"></div>' 
        }; 
       }) 
       .directive('paneContent', function() { 
        return { 
         restrict: 'EA', 
         require: '^paneHeader', 
         transclude: true, 
         replace: true, 
         template: '<div ng-transclude class="angular-accordion-pane-content" ng-show="expanded"></div>' 
        }; 
       }) 
       .controller('outerController', ['$scope', function($scope) { 
        $scope.outerControllerData = [1, 2, 3]; 
       }]); 
    </script> 
</body> 
</html> 

这里的在那里我坚持做同样与Ember:

的index.html

<!DOCTYPE html> 
<html> 
    <body> 
     <script src="//cdnjs.cloudflare.com/ajax/libs/require.js/2.1.9/require.js" data-main="main.js"></script> 
    </body> 
</html> 

main.js

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' 
       }] 
     } 
    }) 
}); 

EmberAccordionComponent.js

define(function(require) { 
    require('ember'); 

    var EmberAccordionComponent = Ember.Component.extend({}); 

    return EmberAccordionComponent; 
}); 

application.hbs

{{outlet}} 

余烬-手风琴header.hbs

<div style="color: blue;"> 
    {{yield}} 
</div> 

烬-手风琴body.hbs

<div style="color: green;"> 
    {{yield}} 
</div> 

index.hbs

{{#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}} 

烬,accordion.hbs

{{#each accordionPaneObject in listOfAccordionPaneObjects}} 
    {{yield}} 
{{/each}} 

-

这是棘手的调试。所以把在:

{{log this.constructor}} 

和:

{{log this}} 

到:

{{#ember-accordion-header}} 

输出以下:

  • Class.model =未定义(为什么? )
  • Ember.ArrayController

我试过重写了Ember的private _yield方法。

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() } 
      }); 
     } 
    } 
}); 

,但我这样做的时候我还没有在我的子组件范围访问accordionPaneObject,和我的{{登录this.constructor}}现在指向:此文章(http://www.thesoftwaresimpleton.com/blog/2013/11/21/component-block/)所建议组件:.EmberAccordionHeaderComponent

所以它看起来像我在某个地方,我只需要再去一级。

当我尝试在EmberAccordionHeaderComponent.js使用此代码:

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() } 
      }); 
     } 
    } 
}); 

我还是不访问accordionPaneObject的,但现在我看到{{登录this.constructor}}输出.EmberAccordionComponent。所以看起来我处于正确的范围,但数据仍然没有约束。

有趣的是,如果我使用任何重新分配在我重写_yield背景和控制器的这些变化,我可以访问数据我用我之后在控制台:

this._parentView._context.content 

回答

6

我有一些意见更新您的代码,请给看看http://emberjs.jsbin.com/ivOyiZa/1/edit

的Javascript

App = Ember.Application.create(); 

App.IndexRoute = Ember.Route.extend({ 
    model: function() { 
    return [ 
     { head: "foo head", body: "foo body " }, 
     { head: "bar head", body: "bar body " }, 
     { head: "ya head", body: "yo body " } 
    ]; 
    } 
}); 

App.EmberAccordionComponent = Ember.Component.extend({ 
    // each accordion header/body item, will have a instance of that view. 
    // so we can isolate the expanded state for each accordion header/body 
    emberAccordionItemView: Ember.View.extend({  
    expanded: false 
    }), 
    _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() } 
     }); 
    } 
    } 
}); 

App.EmberAccordionHeaderComponent = Ember.Component.extend({ 
    classNames: ['ember-accordion-header'], 
    click: function() { 
    // here we toggle the emberAccordionItemView.expanded property 
    this.toggleProperty('parentView.expanded'); 
    } 
}); 

模板

<script type="text/x-handlebars" data-template-name="index"> 
    {{#ember-accordion listOfAccordionPaneObjects=model}}       
      {{#ember-accordion-header}} 
       {{head}} <!-- each object passed in listOfAccordionPaneObjects=model can be accessed here --> 
      {{/ember-accordion-header}} 
      {{#ember-accordion-body}} 
       {{body}} <!-- each object passed in listOfAccordionPaneObjects=model can be accessed here --> 
      {{/ember-accordion-body}}   
    {{/ember-accordion}}   
    </script> 

    <script type="text/x-handlebars" data-template-name="components/ember-accordion">  
    {{#each listOfAccordionPaneObjects itemViewClass="view.emberAccordionItemView"}}    
     <div class="ember-accordion-container"> 
     <div class="ember-accordion-pane">   
      {{yield}}   
     </div> 
     </div>  
    {{/each}} 
    </script> 

    <script type="text/x-handlebars" data-template-name="components/ember-accordion-header">   
    {{yield}}  
    </script> 

    <script type="text/x-handlebars" data-template-name="components/ember-accordion-body">  
    <!-- when EmberAccordionHeaderComponent.click is called, the expanded property change and the content can be visible or not, based on expanded truth --> 
    {{#if parentView.expanded}} 
     <div class="ember-accordion-pane-content"> 
     {{yield}} 
     </div> 
    {{/if}} 
    </script> 

的CSS

.ember-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; 
} 

.ember-accordion-container { 
    height: 100%; 
    width: 100%; 
} 

.ember-accordion-pane { 
    padding: 2px; 
} 

.emberaccordionheaderselected { 
    background-color: #bbb; 
    color: #333; 
    font-weight: bold; 
} 

.ember-accordion-header:hover { 
    text-decoration: underline !important; 
} 

.emberaccordionheaderselected:hover { 
    text-decoration: underline !important; 
} 

.ember-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; 
} 

.emberdisabledpane { 
    opacity: .2; 
} 
+0

非常感谢Marcio!这看起来很棒,我会尽快查看。 – davidjnelson

+0

谢谢Marcio,这真是太棒了!让它起作用的关键是什么? emberAccordionItemView看起来是关键。 – davidjnelson

+1

欢迎您!忽略'_yield'技巧。 'emberAccordionItemView'是一个重要的部分,因为我们为每个手风琴头部和身体分离出'expanded'属性。 –

0

是的,这很容易做到。

下面是一个非常简单的无风格示例,它在悬停而不是点击,但如果取消注释,则单击位于jsbin中,并注释掉mouseenter/mouseleave函数。

http://emberjs.jsbin.com/ijEwItO/3/edit

<script type="text/x-handlebars" data-template-name="components/unicorn-accordian"> 
    <ul> 
    {{#each item in content itemController='unicornItem' itemView='unicornItem'}} 
     <li>{{item.title}} 
     {{#if bodyVisible}} 
     <br/> 
     {{item.body}} 
     {{/if}} 
     </li> 
    {{/each}} 
    </ul> 
</script> 


App.UnicornAccordianComponent = Em.Component.extend(); 

App.UnicornItemController = Em.ObjectController.extend({ 
    bodyVisible: false 
}); 

App.UnicornItemView = Em.View.extend({ 
    mouseEnter: function(){ 
    this.set('controller.bodyVisible', true); 
    }, 

    mouseLeave: function(){ 
    this.set('controller.bodyVisible', false); 
    } 
}); 
+0

感谢,但是我正在寻找给调用者一个更全面的API。我会更新这个问题。 – davidjnelson

+0

这是我拍摄的api:{{#ember-accordion listOfAccordionPaneObjects = model}} {{#ember-accordion-heading}} 标题模板html {{accordionPaneObject。这是手风琴琴体{{accordordPaneObject.lastName}} {{/ ember-accordion-body}} {{#ember-accordion-body}} {{#ember-accordion-body}} {{ {{/ ember-accordion}} – davidjnelson

0

肯定是一个更容易对实现解决方案是将视图(或其他父项)作为参数传递给组件。这将使您可以访问视图的所有属性,同时仍保留使用包含组件的优点。例如:

{{#ember-accordion listOfAccordionPaneObjects=model info=view}}{{!-- Pass view in here--}} 

    {{log view.info}}{{!-- This will log what view.parentView would have done--}} 

    {{ember-accordion-heading firstName=accordionPaneObject.firstName}} 

    {{ember-accordion-body lastName=accordionPaneObject.lastName}} 

{{/ember-accordion}} 

页眉模板会是这个样子:

Header template html here {{firstName}} 

和你的身体范本看起来是这样的:

Body html here {{lastName}} 
+0

谢谢邓肯。那么如何解决可以访问listOfAccordionPaneObjects中每个元素的ember-accordion-heading问题? – davidjnelson

+0

那么你可以将任何你想要的东西传入#ember-accordion-heading,所以类似的方法可以从它属于的任何对象传递对象accordionPaneObject。不过,从你的问题来看,似乎#ember-accordion-heading只会将{{firstName}}包装在html布局中,在这种情况下,部分或纯朴的hbs/handlebars将是首选,并且不会更改首先是组件。 –

+1

尽管如此,我补充说这样的解决方案可能会降低您希望人们使用手风琴的最小努力,API类似的体验。 –

相关问题