【问题标题】:how to access parent component scope from a child components scope in ember?如何从 ember 中的子组件范围访问父组件范围?
【发布时间】:2013-12-08 03:57:23
【问题描述】:

我很好奇这在 ember 中是否可行。这在 Angular 中很容易做到(plunkr:http://plnkr.co/edit/O2e0ukyXdKMs4FcgKGmX?p=preview):

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

我希望调用者能够使用的 api 是这样的(就像 angular 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}}

这是我使用 angular 编写的一个工作示例:

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

应用程序.hbs

{{outlet}}

ember-accordion-header.hbs

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

ember-accordion-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}}

ember-accordion.hbs

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

--

这很难调试。所以放入:

{{log this.constructor}}

还有:

{{log this}}

进入:

{{#ember-accordion-header}}

输出以下内容:

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

我已尝试按照本文 (http://www.thesoftwaresimpleton.com/blog/2013/11/21/component-block/) 的建议覆盖 Ember.Component 的私有 _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() }
            });
        }
    }
});

但是当我这样做时,我仍然无法在我的子组件范围内访问手风琴PaneObject,我的 {{log this.constructor}} 现在指向:.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,但现在我看到 {{log this.constructor}} 正在输出 .EmberAccordionComponent。所以看起来我在正确的范围内,但数据仍然没有绑定。

有趣的是,如果我在重写的 _yield 中使用任何这些重新分配上下文和控制器的变体,我可以使用以下命令在控制台中访问我所追求的数据:

this._parentView._context.content

【问题讨论】:

    标签: api ember.js components declarative transclusion


    【解决方案1】:

    当然,更容易实现的解决方案是将视图(或其他父级)作为参数传递给组件。这将使您能够访问视图的所有属性,同时仍保留使用包含组件的优势。例如:

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

    【讨论】:

    • 谢谢邓肯。这将如何解决 ember-accordion-heading 可以访问 listOfAccordionPaneObjects 中的每个元素的问题?
    • 好吧,你可以将任何你想要的东西传递给#ember-accordion-heading,所以类似的方法可以从作为属性的任何对象传递对象accordionPaneObject。但是,从您的问题来看,似乎 #ember-accordion-heading 仅用于将 {{firstName}} 包装在 html 布局中,在这种情况下,首选部分或普通的 hbs/handlebars 并且不会改变首先是组件。
    • 不过,我补充说,这样的解决方案可能会降低您希望人们使用手风琴时所需要的最小努力、类似 API 的体验。
    • @davidjnelson - 我意识到我应该用这个例子编辑我的答案,所以看看我上面添加的内容是否有帮助。
    【解决方案2】:

    我用一些 cmets 更新了你的代码,请看一下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;
    }
    

    【讨论】:

    • 非常感谢马尔西奥!这看起来很棒,我会尽快检查一下。
    • 感谢 Marcio,这太棒了!让它发挥作用的关键是什么? emberAccordionItemView 看起来是关键。
    • 不客气!忽略_yield 技巧。 emberAccordionItemView 是一个重要部分,因为我们为每个手风琴头和身体隔离了expanded 属性。
    • 我认为忽略_yield 技巧是错误的。事实上,这个技巧在最新版本的 Ember 中破坏了这个实现。相反,您可以以更加组合的方式使用组件来实现类似的结果:emberjs.jsbin.com/korujadupu/2/edit?html,js,output 这里我使用了一个技巧(即在 init 上运行的 setTarget 方法)允许EmberAccordionHeaderComponent 发送的操作冒泡到周围的EmberAccordionItemComponent
    【解决方案3】:

    是的,这很容易做到。

    这是一个非常简单的、没有样式的示例,它是悬停而不是单击,但如果取消注释,单击是在 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); 
      }
    });
    

    【讨论】:

    • 谢谢,但我正在为调用者寻找更全面的 api。我会更新问题。
    • 这是我要拍摄的 api:{{#ember-accordion listOfAccordionPaneObjects=model}} {{#ember-accordion-heading}} 标题模板 html {{accordionPaneObject.firstName}} { {/ember-accordion-heading}} {{#ember-accordion-body}} 这是手风琴主体 {{accordionPaneObject.lastName}} {{/ember-accordion-body}} {{/ember-accordion}}
    猜你喜欢
    • 2021-04-08
    • 2018-09-22
    • 1970-01-01
    • 2018-11-09
    • 2014-12-09
    • 1970-01-01
    • 2021-04-23
    • 2022-08-16
    • 2023-04-08
    相关资源
    最近更新 更多