0

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;
});
dennismonsewicz
  • 25,132
  • 33
  • 116
  • 189
  • Could you post 1. Where the fetch is being called, 2. the `collection` definition, please? – seebiscuit Jul 10 '15 at 18:31
  • 1
    This may sound weird (and probably won't be the fix) but can you try replacing `this.children.findByIndex(this.children.length - 1)` with `this.children.last()`. There's an issue with `findByIndex()` ( see [babysitter#49](https://github.com/marionettejs/backbone.babysitter/issues/49)) – seebiscuit Jul 10 '15 at 18:47
  • @seebiscuit - updated question with code. I tried the `this.children.last()` but still got the same thing :/ – dennismonsewicz Jul 13 '15 at 00:53

1 Answers1

2

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.

seebiscuit
  • 4,905
  • 5
  • 31
  • 47
  • Wow, thank you!! That seemed to fix the "strange output" problem. The problem I am facing now is just that the new ad is being dropped in after the last child is added (not after each fetch; supposed to drop a new ad in after every fetch, or after every 20 records are fetched). I updated my code where I add in the ad to the DOM – dennismonsewicz Jul 13 '15 at 12:17
  • You're welcome. I'm not sure I understand the new issue. Would you mind accepting this answer and explaining the new issue in more detail in a new question? Specifically, show where the `adGeneration` method is called. – seebiscuit Jul 13 '15 at 12:25
  • certainly! You've been a huge help! Thank you!! – dennismonsewicz Jul 13 '15 at 12:33
  • here is my new question http://stackoverflow.com/questions/31383640/marionette-drop-in-new-itemview-after-every-fetch-not-working – dennismonsewicz Jul 13 '15 at 12:37