移动Ember Metal Views而无需重新渲染(拖放)

时间:2015-01-27 21:26:33

标签: ember.js

我们利用draggable-each项目来实现拖放功能,避免重新渲染移动的视图。我试图让这与Ember 1.9和金属视图一起使用。我一直试图修复变形而不会触发重新渲染子视图。这适用于在一个可拖动的每个视图中拖动,但不能在它们之间拖动或插入新的draggable-each时。视图得到重新渲染,我还没弄清楚原因。

有人可以深入了解如何在金属视图中完成渲染以及在不触发重新渲染的情况下移动视图的最佳方法吗?

感谢。

我目前的版本是:

define('components/draggable-each', [], function () {

var a_slice = Array.prototype.slice;

var get = Ember.get;

return Ember.CollectionView.extend(Ember.TargetActionSupport, {
    classNames: ['ember-drag-list'],
    content: Ember.computed('context', function () {
        return this.get('context');
    }),
    handleSelector: null,
    itemSelector: '.draggable-item',
    target: Ember.computed.oneWay('controller'),
    init: function () {
        var itemView = this.get('itemView');
        var ItemViewClass;

        if (itemView) {
            ItemViewClass = this.container.lookupFactory('view:' + itemView);
        } else {
            ItemViewClass = this.get('itemViewClass');
        }

        this.set('itemViewClass', ItemViewClass.extend({
            context: Ember.computed.oneWay('content'),
            template: this.get('template'),
            classNames: ['draggable-item']
        }));

        this.updateRefCount = 0;

        this._super.apply(this, arguments);
    },

    nonVirtualChildViews: function () {
        var children = this._childViews;
        var nonVirtualChildren = [];

        for (var i = 0; i < children.length; i++) {
            var grandchildren = children[i].get('childViews');
            if (grandchildren) {
                for (var y = 0; y < grandchildren.length; y++) {
                    nonVirtualChildren.push(grandchildren[y]);
                }
            }
        }

        return nonVirtualChildren;
    },

    // lifted from Ember.Compontent
    sendAction: function (action) {
        var actionName;
        var contexts = a_slice.call(arguments, 1);

        // Send the default action
        if (action === undefined) {
            actionName = get(this, 'action');
        } else {
            actionName = get(this, action);
        }

        // If no action name for that action could be found, just abort.
        if (actionName === undefined) { return; }

        this.triggerAction({
            action: actionName,
            actionContext: contexts
        });
    },

    viewReceived: function (view /*, source */) {
        view.set('parentView', this);
        //view.set('parentView', this.get('parentView'));
        view.set('_parentView', this);
    },

    arrayWillChange: function () {
        if (this.updateDisabled()) { return; }
        this._super.apply(this, arguments);
    },

    arrayDidChange: function () {
        if (this.updateDisabled()) { return; }
        this._super.apply(this, arguments);
    },

    updateDisabled: function () {
        return this.updateRefCount > 0;
    },

    execWithoutRerender: function (func) {
        this.updateRefCount++;

        try {
            return func();
        } finally {
            this.updateRefCount--;
        }
    },

    // Note: entryView is optional--if not provided, view will render anew from entry
    //       skipNotify is also optional, and if not present, the itemWasInserted action will be sent
    // NOTE: make sure to call viewReceived outside of "updateDisabled" block if entryView already exists (i.e. is being moved from elsewhere)
    insertItem: function (index, entry, entryView, skipNotify, eventContext) {
        var list = this.get('context');
        if (entryView) {
            console.log('+++ adding view ' + entryView.get('elementId'));
        } else {
            console.log('+++ adding new view');
        }

        if (entryView) {
            //insert the view
            this._insertView(entryView, index);
            //update the DOM to reflect
            //index > 0 ? entryView.$().insertAfter(this._childViews[index - 1].$()) : this.$().prepend(entryView.$());
        }

        list.insertAt(index, entry);

        if (!skipNotify) {
            this.sendAction('itemWasInserted', entry, index, list, eventContext);
        }
    },

    // skipNotify is optional, and if not present, the itemWasInserted action will be sent
    removeItem: function (index, isDelete, skipNotify, eventContext) {
        var list = this.get('context');

        var object = list.objectAt(index);
        var entry = object.isController ? object.get('content') : object;
        var view = this._childViews[index];
        var elementId = view.get('elementId');

        console.log('--- removing view ' + elementId);

        //only remove from the childViews array once--if update is not disabled, then the view will be destroyed upon removing from the model,
        //and thus also removed from the views list... so if we remove here as well as in listeners, we might remove multiple views
        if (this.updateDisabled()) {
            this._removeView(view, index);
        }
/*
        if (!isDelete) {
            //if it is not a delete, we need to do a $().detach() to prevent data from being deferenced for GC
            view.$().detach();
        }
        else if (this.updateDisabled()) {
            view.remove();
        }
*/
        //remove from the backing array model, causing the view to be removed if update is not disabled
        list.removeAt(index);
        Ember.propertyDidChange(this, 'childViews');

        if (!skipNotify) {
            this.sendAction('itemWasRemoved', entry, index, list, eventContext);
        }

        return {
            entryModel: entry,
            entryView: view
        };
    },

    replaceItem: function (index, replacement, skipNotify, eventContext) {
        var list = this.get('context');

        var object = list.objectAt(index);
        var entry = object.isController ? object.get('content') : object;
        var view = this._childViews[index];

        //remove from the backing array model, causing the view to be removed if update is not disabled,
        //and add in the replacement at that same location
        list.replace(index, 1, [replacement]);

        if (!skipNotify) {
            this.sendAction('itemWasReplaced', entry, index, list, eventContext);
        }

        return {
            entryModel: entry,
            entryView: view
        };
    },

    moveItem: function (oldIndex, newIndex, source, eventContext) {
        var self = this;
        var sourceList = source.get('content');
        var targetList = this.get('content');

        var object = sourceList.objectAt(oldIndex);
        var entry = object.isController ? object.get('content') : object;
        //var viewToMove = source._childViews[oldIndex];
        //source._renderer.remove(view, false, true);
        //var view = source._childViews.splice(oldIndex, 1)[0];

        var doMove = function () {
            var viewToMove = source._childViews.splice(oldIndex, 1)[0];
            var resolvedNewIndex = newIndex + (self === source && oldIndex < newIndex ? -1 : 0);

            self._insertView(viewToMove, resolvedNewIndex);
            source._removeView(viewToMove, oldIndex);

            sourceList.removeAt(oldIndex);
            targetList.insertAt(resolvedNewIndex, entry);
            viewToMove.set('content', targetList.objectAt(resolvedNewIndex)); // needed when using item controllers that will get destroyed subsequent to the removeAt operation

            Ember.propertyDidChange(source, 'childViews');
            Ember.propertyDidChange(self, 'childViews');

            //var info = source.removeItem(oldIndex, false, true);
            //that.insertItem(resolvedNewIndex, info.entryModel, info.entryView, true);

            //return info;

            return {
                entryModel: entry,
                entryView: viewToMove
            };
        };

        var info = this.execWithoutRerender(function () {
            return source.execWithoutRerender(doMove, this);
        }, this);


        this.sendAction('itemWasMoved', info.entryModel, oldIndex, newIndex, source, eventContext);
    },

    // tell morph shadow DOM that child view should have this draggable-each as 
    // parent view now
    _insertView: function (view, newIndex) {
        //set the parentView of the new child
        this.viewReceived(view);
        view._morph = this._childViewsMorph.insert(newIndex, view.element);
        this._childViews.splice(newIndex, 0, view);
    },

    _removeView: function(view, index) {
        this._childViewsMorph.removeMorph(view._morph);
        this._childViews.splice(index, 1);
    }
});
});

0 个答案:

没有答案
相关问题