Marionette CompositeView children.findByIndex 集合同步后未按预期工作

Marionette CompositeView children.findByIndex not working as expected after collection sync

我有以下代码发生在集合同步之后:

    adGeneration: function() {
        var child = this.children.findByIndex(this.children.length - 1);
        console.log(child.model.toJSON());
        eventer.trigger('generate:new:ad', child);
    },

我 运行 遇到的问题是,在第一次同步之后,child 元素是一个空白模型:

第一次:

Object {id: "5-vp39kv3uiigxecdi", size: 26, price: "9.84", face: "( ⚆ _ ⚆ )"}

每次之后:

Object {length: 40, models: Array[41], _byId: Object, _listeningTo: Object, _listenId: "l14"…}

产品系列

define(["backbone", "lodash", "fonts/products/model", "eventer"],
function(Backbone, _, ProductModel, eventer) {
    'use strict';

    var ProductsCollection = Backbone.Collection.extend({
        model: ProductModel,

        sort_key: 'price',

        url: '/api/products',

        offset: 0,

        initialize: function() {
            this.listenTo(eventer, 'fetch:more:products', this.loadMore, this);
        },

        comparator: function(item) {
            return item.get(this.sort_key);
        },

        sortByKey: function(field) {
            this.sort_key = field;
            this.sort();
        },

        parse: function(data) {
            return _.chain(data)
                    .filter(function(item) {
                        if(item.id) {
                            return item;
                        }
                    })
                    .map(function(item){
                        item.price = this.formatCurrency(item.price);

                        return item;
                    }.bind(this))
                    .value();
        },

        formatCurrency: function(total) {
            return (total/100).toFixed(2);
        },

        loadMore: function() {
            this.offset += 1;
            this.fetch({
                data: {
                    limit: 20,
                    skip: this.offset
                },
                remove: false,
                success: function(collection) {
                    this.add(collection);
                }.bind(this)
            });
        }
    });

    return ProductsCollection;

});

包含产品集合视图的布局视图。该集合是在布局视图的显示上获取的

define(["marionette", "lodash", "text!fonts/template.html",
"fonts/controls/view", "fonts/products/view", "fonts/products/collection", "eventer"],
function(Marionette, _, templateHTML, ControlsView, ProductsView,
    ProductsCollection, eventer) {
    'use strict';

    var FontsView = Marionette.LayoutView.extend({

        regions: {
            controls: '#controls',
            products: '#products-list'
        },

        template: _.template(templateHTML),

        initialize: function() {
            this._controlsView = new ControlsView();
            this._productsView = new ProductsView({
                collection: new ProductsCollection({
                    reorderOnSort: false,
                    sort: false
                })
            });

            this.listenTo(this._productsView.collection, 'sync', this.loading, this);
            this.listenTo(eventer, 'fetch:more:products', this.loading, this);
            this.listenTo(eventer, 'products:end', this.productsEnd, this);
        },

        onRender: function() {
            this.getRegion('controls').show(this._controlsView);
            this.getRegion('products').show(this._productsView);
            this.loading();
        },

        onShow: function() {
            this._productsView.collection.fetch({
                data: {
                    limit: 20
                }
            })
        },

        productsEnd: function() {
            this.loading();
            this.$el.find('#loading').html("~ end of catalogue ~")
        },

        loading: function() {
            var toggle = this.$el.find('#loading').is(':hidden');
            this.$el.find('#loading').toggle(toggle);
        }
    });

    return FontsView;

});

广告视图:

define(["marionette", "lodash", "text!ads/template.html", "eventer"],
function(Marionette, _, templateHTML, eventer) {
    'use strict';

    var AdsView = Marionette.ItemView.extend({
        template: _.template(templateHTML),

        ui: {
            ad: '.ad'
        },

        initialize: function() {
            this.listenTo(eventer, 'generate:new:ad', this.generateNewAd, this);
        },

        onShow: function() {
            // Set add image onShow
            this.ui.ad.prop('src', '/ad/' + this.randomNumber());
        },

        generateNewAd: function(childView) {
            var newAd = this.ui.ad.clone(),
                element = childView.$el,
                elementId = childView.model.get("id");

            newAd.prop('src', '/ad/' + this.randomNumber());

            $("#" + elementId).after(newAd);
        },

        randomNumber: function() {
            return Math.floor(Math.random()*1000);
        },

        setUpAd: function() {
            this.ui.ad.prop('src', '/ad/' + this.randomNumber());
        }
    });

    return AdsView;
});

我认为您的问题出在 ProductsCollection.loadMore 方法中。在那里,在 success 回调到你的 fetch 你做的,

function(collection) { this.add(collection); }

幕后发生的事情是,在调用 success 回调之前,Backbone 将首先 运行 Collection.set() 处理您的数据。默认情况下,在 set 中,您的数据将被解析为由 ProductsCollection.parse 返回的模型数组,如果发现任何新模型,它们将被 add 编辑到您现有的 collection(请注意,除非您将 { remove: false } 传递给您的 fetch 选项,否则您 collection 中的模型在您上次获取时 而不是 将被删除。参见 Collection.set)

那么,当您在 loadMore 中执行 fetch 时会发生什么,这称为 第一个 fetch、Backbone 将首先 add 服务器中的所有模型(从 ProductsCollection.parse 返回),然后调用 fetchsuccess 回调,这实际上是最后一个addaddProductsCollection 实例。不是 collection.models,一个模型数组,而是一个 Backbone object,它有一个 属性 models,其中包含一个原始模型数组。因此,奇怪的输出:

Object {length: 40, models: Array[41], _byId: Object, _listeningTo: Object, 
_listenId: "l14"…}

只需删除 success 回调(这是不必要的),您 ProductsView 的最后一个 child 视图应该是从返回的最后一个模型呈现的视图。