不重新计算基于数组值的计算属性

时间:2012-11-27 02:50:34

标签: javascript coffeescript ember.js

问题空间

我正在渲染一些嵌套的Ember视图,因此我可以创建一个分割器窗格样式的UI。我想在第一次渲染时调整我的视图大小,以便它们具有相同的宽度。我不希望我的孩子视图相互看,所以我使用Ember.ContainerView的子类来保存我的内容和可拖动的分割器句柄。

我无法在容器视图上使用Ember.View#didInsertElement,因为我需要等待我的子视图完全呈现。

我的(尝试过的)解决方案

我正在使用此答案中提供的代码:How to wait for a template to be fully rendered。这会向所有isRendered个实例添加一个属性Ember.View,当模板通过重新打开Ember激活didInsertElement时会自动设置该实例。

Ember.View.reopen

  didInsertElement: ->
    res = @_super();
    @_setIsRendered();
    res

  _setIsRendered: ->
    if (!! @$())
      @set('isRendered', true)
    else
      Ember.run.next this, ->
        @_setIsRendered()

我尝试重新打开Ember.ContainerView以向所有容器视图添加childViewsRendered属性,但是Ember反对并为容器视图抛出了一些非常奇怪的IndexOutOfBounds错误,其中只有一个项目位于childViews中。

我最终将我的收藏代码放在以下mixin中:

App.ChildrenRendered = Ember.Mixin.create

  childViewsRendered: (->
    res = @get('childViews').everyProperty('isRendered')
    console.log('childViewsRendered', res, this)

    # Pointer to this most offensive object for debugging
    window.wtf = this
    res
  ).property('childViews.@each.isRendered')

  _runChildViewsDidRender: (->
    if @get('childViewsRendered')
      console.log('trying to invoke childViewsDidRender')
      Ember.tryInvoke(this, 'childViewsDidRender')
  ).observes('childViewsRendered')

然后我有一个这样的课:

App.SplitterView = Ember.ContainerView.extend App.ChildrenRendered,
  # ...(some properties)...
  init: ->
    child_views = @get('childViews')
    child_views.pushObjects([App.WindowView.create(), App.WindowView.create()])

什么有效:

  • App.SplitterView#childViewsRendered在任何视图呈现之前计算一次,因此变为false
  • Ember处理(插入和呈现)视图,并将自己的isRendered属性设置为精细和花花公子。
  • 稍后运行window.wtf.get('childViews').everyProperty('isRendered')会返回true

什么行不通:

  • 计算属性childViewsRendered永远不会再次自我更新。
  • childView数组元素成员上虚拟值的计算属性似乎也不起作用。

1 个答案:

答案 0 :(得分:0)

没有jsFiddle,我只能看到你所说的和你提供的片段,但似乎你要做的就是确保容器的所有childViews都在DOM之前做 某事 ,是吗?

如果是这种情况......当视图添加到childViews时,正如您所知,它们会自动呈现并插入到DOM中。这一切都发生在同一个RunLoop中。因此,要延迟执行某些函数,直到所有子节点都是“inDOM”,就像观察childViews.@each并使用Ember.run.next将执行延迟到下一个RunLoop一样简单。这是一个例子

App.SomeContainerView = Em.ContainerView.extend
  init: ->
    @_super() # run default init
    # add 2 of our 4 views
    @get('childViews').pushObjects [@aView.create(), @bView.create()]

  # a non-cached (volatile) computed property to check if children are inDOM
  # just used for demo purposes
  childrenAreInDom: (->
    @get('childViews').every (v) ->
      v.state is "inDOM"
  ).property().volatile()

  # our observer
  observeChildren: (->
    # if the container isn't inDOM yet the afterRender function we add below 
    # will handle it, if views are added/removed after that then we handle it here
    return unless @state is "inDOM"
    # output to console if every childview are inDOM
    console.log "observeChildren", @get('childrenAreInDom')
    # the above will always be false since we're still in the same RunLoop
    # next RunLoop our function will run
    Ember.run.next @, 'doWhatever'
  ).observes('childViews.@each') # observe changes in the childViews array

  afterRender: ->
    console.log "afterRender", @get('childrenAreInDom')
    Ember.run.next @, 'doWhatever'

  # the function we want to run eventually after children are inDOM
  doWhatever: ->
    console.log "doWhatever"
    # print out childrenAreInDom one more time.. it will be true
    console.log "childrenAreInDom:", @get('childrenAreInDom')

  # some views to insert
  aView: Em.View.extend
    template: Em.Handlebars.compile("A")
  bView: Em.View.extend
    template: Em.Handlebars.compile("B")
  cView: Em.View.extend
    template: Em.Handlebars.compile("C")
  dView: Em.View.extend
    template: Em.Handlebars.compile("D")

如果你在模板中有{{view App.SomeContainerView}},你会在控制台中看到:

afterRender false
doWhatever
childrenAreInDom true 

如果您以编程方式添加cView&在容器已经在DOM中之后通过pushObjects进行dView,你会看到

observeChildren false
doWhatever
childrenAreInDom true 

即使这不是你想要的,希望它可以帮助你在没有所有Mixin废话的情况下到达你需要的位置:D