其中通过在对象的列表声明余烬组件要呈现/约束,同时传递状态嵌套组件可以访问? [英] 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.
这很容易在角:
<!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
下面是我的企图迄今为止烬做:
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
在哪里我坚持的是:
- 为什么不是参数'测试'的余烬,手风琴头组件内部访问?如果我直接把它定义在外部组件中的观点,我可以访问它。
- 如何避免将view.parentView在参数前面我想访问,即:view.parentView.specifiedInComponent?这不是简单的API的消费者。
- 为什么我必须重写烬的私有方法得到这个远(_yield)。覆盖私有成员是一个坏主意,因为他们可以烬的版本之间切换。
谢谢!
推荐答案
您有我的位置:这很容易在角:P
You had me at: "This is easy in angular" :P
这是没有必要重写_yield。默认行为允许您访问通过使用parentView定义视图属性{{view.property}}。当嵌套组件,这意味着属性递归流传下来。同时您忘记设置测试=测试
灰烬手风琴头组件。
It's not necessary to override _yield. The default behaviour allows you to access view properties defined in the parentView by using {{view.property}}. 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.
下面是一个工作版本: http://jsbin.com/apIYurEN/6/
Here is a working version: http://jsbin.com/apIYurEN/6/
这篇关于其中通过在对象的列表声明余烬组件要呈现/约束,同时传递状态嵌套组件可以访问?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!