我有一个名为in-view
的Ember组件的mixin,其作用是请求将元素放入视图中。它提供了一个属性,其值是要进入视图的一段内容,如果该属性与组件的内容匹配,那么我调用scrollIntoView
或等价物。代码看起来像这样:
// calling template
{{#each items as |item|}}
{{my-item content=item inViewItem=inViewItem}}
}}
// mixins/in-view.js
scrollIntoView() {
if (this.get('content') === this.get('inViewItem'))
this.get('element').scrollIntoView();
}.on('didInsertElement')
// components/my-item/component.js
import InView from 'mixins/in-view';
export default Ember.Component.extend(InView,
这很好用。当我想要更改视图中的项目时,我出现了这个问题。我可以让in-view
mixin观察inviewItem
属性:
}.on('didInsertElement').observes('inViewItem')
这也有效,但看起来有点像代码味道。
另外,我的实际代码结构是有一个控制器知道哪个项目应该在视图中,然后它的模板调用my-item-list
组件,该组件显示包含项目列表的可滚动div,以及然后调用my-item
组件。这意味着我必须将inViewItem
属性从控制器向下传递到两个级别,如
// resource/controller.js
inViewItem: something
// resource/template.js
{{my-item-list items=item inViewItem=inViewItem}}
// components/my-item-list/template.js
{{#each items as |item|}}
{{my-item content=item inViewItem=inViewItem}}
}}
我可以通过硬件连接my-item
模板来访问控制器上的inViewItem
属性来避免这种情况:
scrollIntoView() {
if (this.get('content') === this.get('controller.inViewItem'))
this.get('element').scrollIntoView();
}.on('didInsertElement')
但这是另一种代码味道;我不想将特定控制器字段的这种依赖性构建到mixin中。相反,我可以将组件控制器属性的名称传递给观察,但这看起来过于笨拙,并且观察名称可变的属性很棘手。更重要的是,当控制器在2.0中消失时,我认为这不会起作用。
我想要的是一种“ping”或以某种方式向模板发送消息的方法。我知道原则上这违反了DDAU原则,但在这种特殊情况下,我需要的是以某种方式发送“动作向下” - 一个动作告诉组件调整自己以使自己进入视野。
当然,我可以放弃in-view
mixin的整个想法,只需让控制器深入查看生成的HTML,找到要进入视图的项目并发出scrollIntoView
它直接。但是,这似乎违反了一些关注点分离的原则; my-item
模板将不再完全控制自己。
此类案例的推荐设计模式是什么?
答案 0 :(得分:1)
这里的解决方案是朝着与你相反的方向前进。这里的组件是一个本地化的范围,您感到痛苦的是您的本地化范围需要访问和改变全局状态(应用程序的滚动位置)。
有些人使用滚动服务来跟踪和改变状态,我自己也使用了几种变体。
听起来好像你正在处理一个可滚动列表,也许是一个div,并且视图中的项目不仅仅是页面状态的函数,而是以编程方式可能会改变。例如,已插入新项目,并且您想要将新项目滚动到视图中。
像jquery.scrollTo或类似的插件(统称"滚动")会比仅仅跳到新位置更好,因为它保留了用户的上下文意识到它们所处的位置页。
使用可滚动的div或列表或类似内容,您可以选择让顶级组件控制滚动状态。在这种情况下,滚动状态仍然是本地化的,但它不是本地化到每个项目,而是作为一个整体被本地化到可滚动区域,这是它最好的所在。
列表项有许多模式可以向父列表组件注册。在一个强有力的场景中,我可能会这样做,但是快速且不是很脏的方法是做一些事情,其中didInsertElement
新孩子向包含它的父母的父母发出一个动作,然后父母那个用于检查它是否为活动项目,如果是,则触发scrollTo。