如何在Ember.js中退出路由时*不*破坏视图

13

关于Ember.js新的路由系统(在这里有介绍),如果我理解正确,当您退出一个路由时,视图将被销毁。

是否有办法避免在退出路由时摧毁视图,并使得当用户重新进入路由时能够保留视图的状态?


更新:看起来,只有当替换了新路由中的outlet视图时,视图才会被销毁。例如,如果您在stateA中使用{{outlet master}}和ViewA,则前往具有{{outlet master}}中的ViewB的stateB,那么ViewB将替换ViewA。解决此问题的一种方法是在需要保留视图时定义多个出口,例如{{outlet master1}},{{outlet master2}}等。

一个很好的功能是能够通过传递一个视图数组到outlet并且能够选择在退出路由时是销毁视图还是仅隐藏它们。


5
Zack,我认为如果你将顶级输出设置为“ContainerView”,那么你可以完成这些额外的功能。然后,您可以通过“childViews”属性直接操作其子项,并控制是否删除子视图或仅隐藏它。 - Roy Daniels
2个回答

9

我已经找出如何修改路由系统,以便不会销毁插入到outlets中的视图。首先我覆盖了Handlebars中的outlet辅助器,使其加载一个Ember.OutletView{{outlet}}中:

Ember.Handlebars.registerHelper('outlet', function(property, options) {
  if (property && property.data && property.data.isRenderData) {
    options = property;
    property = 'view';
  }

  options.hash.currentViewBinding = "controller." + property;

  return Ember.Handlebars.helpers.view.call(this, Ember.OutletView, options);
});

Ember.OutletView继承了Ember.ContainerView,具体如下:

Ember.OutletView = Ember.ContainerView.extend({
    childViews: [],

    _currentViewWillChange: Ember.beforeObserver( function() {
        var childViews = this.get('childViews');

            // Instead of removing currentView, just hide all childViews
            childViews.setEach('isVisible', false);

    }, 'currentView'),

    _currentViewDidChange: Ember.observer( function() {
        var childViews = this.get('childViews'),
            currentView = this.get('currentView');

        if (currentView) {
            // Check if currentView is already within childViews array
            // TODO: test
            var alreadyPresent = childViews.find( function(child) {
               if (Ember.View.isEqual(currentView, child, [])) {          
                   return true;
               } 
            });

            if (!!alreadyPresent) {
                alreadyPresent.set('isVisible', true);
            } else {
                childViews.pushObject(currentView);
            }
        }
    }, 'currentView')

});

基本上我们重写了_currentViewWillChange(),只需隐藏所有的childViews而不是删除currentView。 然后在_currentViewDidChange()中,我们检查currentView是否已经在childViews中,并相应地进行操作。 Ember.View.isEqualUnderscoreisEqual的修改版本:

Ember.View.reopenClass({ 
    isEqual: function(a, b, stack) {
        // Identical objects are equal. `0 === -0`, but they aren't identical.
        // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
        if (a === b) return a !== 0 || 1 / a == 1 / b;
        // A strict comparison is necessary because `null == undefined`.
        if (a == null || b == null) return a === b;
        // Unwrap any wrapped objects.
        if (a._chain) a = a._wrapped;
        if (b._chain) b = b._wrapped;
        // Compare `[[Class]]` names.
        var className = toString.call(a);
        if (className != toString.call(b)) return false;

        if (typeof a != 'object' || typeof b != 'object') return false;
        // Assume equality for cyclic structures. The algorithm for detecting cyclic
        // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
        var length = stack.length;
        while (length--) {
            // Linear search. Performance is inversely proportional to the number of
            // unique nested structures.
            if (stack[length] == a) return true;
        }
        // Add the first object to the stack of traversed objects.
        stack.push(a);
        var size = 0, result = true;
        // Recursively compare objects and arrays.
        if (className == '[object Array]') {
            // Compare array lengths to determine if a deep comparison is necessary.
            size = a.length;
            result = size == b.length;
            if (result) {
                // Deep compare the contents, ignoring non-numeric properties.
                while (size--) {
                    // Ensure commutative equality for sparse arrays.
                    if (!(result = size in a == size in b && this.isEqual(a[size], b[size], stack))) break;
                }
            }
        } else {
            // Objects with different constructors are not equivalent.
            if (a.get('constructor').toString() != b.get('constructor').toString()) {
                return false;
            }

            // Deep compare objects.
            for (var key in a) {
                if (a.hasOwnProperty(key)) {
                    // Count the expected number of properties.
                    size++;
                    // Deep compare each member.
                    if ( !(result = b.hasOwnProperty(key) )) break;
                }
            }
        }
        // Remove the first object from the stack of traversed objects.
        stack.pop();
        return result;
    }
});

1
一个 containerView 比把所有这些自定义逻辑放在里面更加合适(这在某种程度上是 containerView 的粗略复制)。这不应该是被接受的解决方案。 - Valer
1
这是一个容器视图,不幸的是,这个逻辑仍然是必要的,以防止视图拆除。 - runspired

4
当用户重新进入路由时,保留视图状态。相反,我会将该信息存储在控制器(或状态管理器)中,以便在重新进入路由时,新视图使用旧状态初始化。这有道理吗?例如,如果它是帖子列表,并且选择了一个帖子,则会在控制器(或状态管理器)中存储关于选择的帖子的数据。访问特定帖子后,再返回列表,同一帖子将被选中。
我可以想象这种情况可能不是很有用(例如,在长列表中滚动到特定位置),因此也许这并没有回答你的问题。

是的,我不是在谈论简单的选择,而是在讨论用户可能已经部分完成的长表单、滚动到长列表(正如你所说的)等情况... 因此,将所有信息存储到控制器中并在重新进入时更新视图是没有意义的。另一种方法是将 View 实例存储在控制器中,并在每次进入路由时使用它。但我希望有更好的方法。 - Panagiotis Panagi
也许还有更好的方法:比我更懂Ember的人(肯定很多)可能会回答。 :) - pjmorse

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接