简体   繁体   English

声明性余烬组件在传递要渲染/绑定的对象列表时传递嵌套组件可以访问的状态?

[英]Declarative ember component which passes in a list of objects to be rendered / bound, while passing state that nested components can access?

I'm having a hard time figuring out how to build an ember component which has nested components which are rendered as a list based on an input list, while also allowing state to be passed in which is accessible from the nested components. 我很难弄清楚如何构建一个ember组件,该组件具有嵌套的组件,这些组件基于输入列表呈现为列表,同时还允许传递状态,以便可以从嵌套的组件访问状态。

This is easy in 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;
        }

        .loading {
            opacity: .2;
        }
    </style>
</head>
<body style="margin: 0;">


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

    <div class="angular-accordion-header" ng-click="fakeXhrService.load()">
        Click here to simulate loading new data.
    </div>
    <angular-accordion list-of-accordion-pane-objects="outerControllerData" loading="fakeXhrService.isLoading()">
        <pane>
            <pane-header ng-class="{loading:loading}">{{accordionPaneObject.firstName}}</pane-header>
            <pane-content>{{accordionPaneObject.lastName}}</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', [])
            .factory('fakeXhrService', ['$timeout', function($timeout) {
                var loading = false,
                    fakeXhrService;

                fakeXhrService = {
                    load: function() {
                        loading = true;

                        $timeout(function() {
                            loading = false;
                        }, 2000);
                    },
                    isLoading: function() {
                        return loading;
                    }
                };

                return fakeXhrService;
            }])
            .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" ng-cloak></div>' +
                            '</div>',
                    controller: ['$scope', function($scope) {
                        var panes = [];

                        this.addPane = function(pane) {
                            panes.push(pane);
                        };
                    }],
                    scope: {
                        listOfAccordionPaneObjects: '=',
                        loading: '='
                    }
                };
            })
            .directive('pane', function() {
                return {
                    restrict: 'E',
                    transclude: true,
                    require: '^angularAccordion',
                    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', 'fakeXhrService', function($scope, fakeXhrService) {
                var people = [],
                    i = 0;

                for(i; i < 10; i++) {
                    people.push({
                        firstName: 'first ' + i.toString(),
                        lastName: 'last ' + i.toString()
                    });
                }

                $scope.outerControllerData = people;

                $scope.fakeXhrService = fakeXhrService;
            }]);
</script>
</body>
</html>

plunkr: http://plnkr.co/edit/NxXAgP8Ba7MK1cz2IGXg?p=preview plunkr: http ://plnkr.co/edit/NxXAgP8Ba7MK1cz2IGXg?p=preview

Here's my attempt so far doing it in ember: 到目前为止,这是我在余烬中所做的尝试:

<!doctype html>
<html>
<head>
    <style>
        .ember-accordion-header {
            background-color: #999;
            color: #fff;
            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-pane {
            padding: 2px;
        }

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

        .ember-accordion-container {}

        .loading {
            opacity: .2;
        }
    </style>
</head>
<body style="margin: 0;">

<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">
    {{#if parentView.expanded}}
        <div class="ember-accordion-pane-content">
            {{yield}}
        </div>
    {{/if}}
</script>

<script type="text/x-handlebars" data-template-name="index">
    from outside the component: {{test}}
    {{#ember-accordion listOfAccordionPaneObjects=model test=test}}
        {{#ember-accordion-header class="header"}}
            {{firstName}}<br />
            child inner scope test defined directly on the view inside the component: {{view.parentView.specifiedInComponent}}<br />
            child inner scope test passed into the component: {{view.parentView.test}}
        {{/ember-accordion-header}}
        {{#ember-accordion-body class="body"}}
            {{lastName}}<br />
        {{/ember-accordion-body}}
    {{/ember-accordion}}

</script>

<script type="text/x-handlebars" data-template-name="application">
    {{outlet}}
</script>


<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.2.1/handlebars.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ember.js/1.2.0/ember.debug.js"></script>
<script>
    var Person = Ember.Object.extend({
            firstName: '',
            lastName: '',
            fullName: function() {
                return this.get('firstName') + ' ' + this.get('lastName');
            }.property()
        }),
        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,
                specifiedInComponent: 'this works, but how to get it from the property passed to the component?'
            }),
            _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() }
                    });
                }
            }
        }),
        EmberAccordionHeaderComponent = Ember.Component.extend({
            classNames: ['ember-accordion-header'],
            classNameBindings: ['expanded'],
            expanded: false,
            click: function() {
                // here we toggle the emberAccordionItemView.expanded property
                this.toggleProperty('parentView.expanded');
                this.toggleProperty('expanded');
            }
        }),
        App = Ember.Application.create(),
        people = [],
        i = 0;

    App.EmberAccordionComponent = EmberAccordionComponent;
    App.EmberAccordionHeaderComponent = EmberAccordionHeaderComponent;

    for(i; i < 10; i++) {
        people.push(Person.create({
            firstName: 'first ' + i.toString(),
            lastName: 'last ' + i.toString()
        }));
    }

    App.IndexRoute = Ember.Route.extend({
        model: function() {
            return people;
        }
    });

    App.IndexController = Ember.Controller.extend({
        init: function() {
            this._super();

            this.set('test', 'TEST WORKED!');
        }
    });
</script>
</body>
</html>

jsbin: http://jsbin.com/apIYurEN/1/edit jsbin: http ://jsbin.com/apIYurEN/1/edit

Where I'm stuck is: 我遇到的困难是:

  1. Why isn't the parameter 'test' accessible inside the ember-accordion-header component? 为什么在ember-accordion-header组件内部无法访问参数“ test”? If I define it directly on the view inside the outer component I can access it. 如果直接在外部组件内部的视图上定义它,则可以访问它。
  2. How can I avoid putting view.parentView in front of the parameter I want to access, ie: view.parentView.specifiedInComponent? 如何避免将view.parentView放在要访问的参数(即view.parentView.specifiedInComponent)的前面? This is not simple for api consumers. 对于api使用者而言,这并不简单。
  3. Why do I have to override a private method of ember to get this far ( _yield ). 为什么我必须重写ember的私有方法才能做到这一点(_yield)。 Overriding private members is a bad idea since they can change between versions of ember. 覆盖私有成员是一个坏主意,因为它们可以在余烬版本之间进行更改。

Thanks! 谢谢!

You had me at: "This is easy in angular" :P 您在以下位置遇到了我:“这在角度上很容易”:P

It's not necessary to override _yield. 不必覆盖_yield。 The default behaviour allows you to access view properties defined in the parentView by using {{view.property}}. 默认行为允许您使用{{view.property}}访问parentView中定义的视图属性。 When nesting components this means that the properties are passed down recursively. 嵌套组件时,这意味着将递归传递属性。 Also you forgot to set test=test on the ember-accordion-header component. 您也忘记在ember-accordion-header组件上设置test=test

Here is a working version: http://jsbin.com/apIYurEN/6/ 这是一个工作版本: http : //jsbin.com/apIYurEN/6/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何从ember中的子组件范围访问父组件范围? - how to access parent component scope from a child components scope in ember? 为什么我无法在 React 中访问某些嵌套的 JSON 对象? - Why can't I access certain nested JSON objects in React? 在 Postman POST 请求中传递对象列表 -Body - Passing list of objects in Postman POST request -Body 传递对象列表以获取 web API 中的方法 - passing list of objects to get method in web API 我可以为类型的 API 响应编写 Pact 合同吗,对象列表可能具有空列表或填充列表 - Can I write Pact contract for an API response of the type, a list of objects which may have either an empty list or a populated list 如何通过 Ruby on Rails 中的 API GET 请求访问(并保存到我的数据库)JSON 数组中的嵌套对象/属性? - How can I access (and save to my database) nested objects/properties within a JSON array via an API GET request in Ruby on Rails? 如何在 React Native flatlist 中访问数组中的嵌套对象 - How to access nested objects in an array in react native flatlist 如何通过URL访问嵌套的Rest API对象 - How to access nested rest api objects via URL 访问 Google Pay API for Passes - Getting access to the Google Pay API for Passes 组件在反应中被渲染 6 次 - Component being rendered 6 times in react
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM