Marionette CompositeView children.findByIndex not

2019-03-04 18:37发布

问题:

I have the following code that happens after a collection sync:

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

The problem I am running into, is that, after the first sync, the child element is a blank model:

First Time:

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

Every time after:

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

ProductsCollection

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;

});

LayoutView that contains the View for the productions collection. The collection is fetched onShow of the layoutview

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;

});

AdsView:

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;
});

回答1:

I think your problem is in the ProductsCollection.loadMore method. There, in the success callback to your fetch you do,

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

What's happening behind the scenes is that before your success callback is invoked, Backbone will first run Collection.set() on your data. By default, inside set your data will be parsed into a array of models returned by ProductsCollection.parse and if any new models are found they will be add'ed to your existing collection (note that unless you pass { remove: false } to your fetch options, models in your collection which are not in your last fetch will be removed. See Collection.set)

So, what happens when you do the fetch in loadMore, which is called after the first fetch, Backbone will first add all the models from the server (that are returned from ProductsCollection.parse) and then invoke the success callback of your fetch, which, essentially does one last add. And what it's add'ing is the ProductsCollection instance. Not collection.models, an array of models, rather a Backbone object that has a property models that holds a raw array of models. Hence, the strange output:

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

Simply remove that success callback (which is unnecessary) and the last child view of your ProductsView should be the view rendered from the last model returned.



标签: marionette