Getting duplicate list item views #107

Closed
dstibrany opened this Issue Jul 19, 2012 · 44 comments

Comments

Projects
None yet
5 participants

I"m sometimes getting duplicate list items on page load:

In my router I'm doing something like:

 var main = app.useLayout('main');
 main.insertViews({
                '#document-nav': [
                    new Document.Views.FolderTree({model: folder_tree}),
                    new Document.Views.TagList({collection: tag_list})
                ],
});
.
.
.
tag_list.fetch()

Then I have this code in my TagList view:

      initialize: function() {
            this.collection.on('reset', this.render, this);
        },

        render: function(manage) {
            // Iterate over the passed collection and create a view for each item.
            this.collection.each(function(model) {
                // Pass the document model data to the new Tag List Item View.
                this.insertView(
                    "#tag-list",
                    new Views.TagListItem({
                        model: model
                    })
                );
            }, this);

            return manage(this).render();
        }

So, I have 10 items in my DB, and sometimes 10 TagListItem Views will be created, sometimes 20.

I'm using a similar style thoughout my app and haven't ran into this problem. The difference in this situation is that the SQL query is very simple and the payload is small, so the data should be coming back from the server quick. But I dont understand why re-rendering the list view wouldn't wipe out existing items.

Any ideas?

I was able to fix it on my end. I changed initialize in my view to this:

        initialize: function() {
            this.collection.on('reset', function() {
                if (this.__manager__.isManaged) {
                    this.render();
                }
            }, this);
        }

It looks like what's happening is that in the situation where 20 items would appear, partials.push(view) on line 188 of LM is appending all 20 items and has no way of knowing that render was called twice on the ListView.

It seems like some weird async stuff is happening too. Like, I would expect that the call I make to this.render (the one that happens after the reset event), would happen AFTER the layout gets rendered, but it's somehow getting fired somewhere in between. I'm pretty confused about what's going on in the event queue.

Owner

tbranyen commented Jul 21, 2012

It would be great to see your code and get a reduced test case from it. I'd love to have a unit test that can reproduce this behavior.

Hmm, I'll see what I can do. The app is for the company I work for, so im gonna run it by my boss before revealing code. How much code do you need to see to get a test case? Do you need to actually run the app?

Owner

tbranyen commented Jul 21, 2012

I just need to be able to reproduce the same error in a unit test so I can patch and not regress on it.

Here's a gist of the related files. https://gist.github.com/3163981

Let me know if that's enough to make a unit test. If it's not, I can try creating one.

Owner

tbranyen commented Jul 23, 2012

Hey @dstibrany I think I patched it in the wip branch, I'm going to backport it today and see if it fixes your issue.

Cool, I'll run it on my code when you're done.

Owner

tbranyen commented Jul 25, 2012

@dstibrany Can you give the latest master a shot? I think I may have nailed the issue.

So I'm not getting duplicates anymore but there's a couple of issues:

-Sometimes the view doesn't render at all. I'm logging out my render function, and it's only executed once when this bug happens. The times that the view is ok, render fires twice (once with a blank collection, once with a full collection)

-Two of my views are being rendered in reverse order, the ones attached to #documents below:

main.insertViews({
    '#header': new Header.Views.Header(),
    '#document-nav': [
        new Document.Views.FolderTree({model: folder_tree}),
        new Document.Views.TagList({collection: tag_list})
     ],
      '#documents': [
          new Document.Views.List({collection: documents}),
          new Document.Views.Preview({model: new Document.Preview()})
     ]
});

Your latest commit 60fd982 fixed reverse order of views.

Owner

tbranyen commented Jul 25, 2012

So this is still happening?

-Sometimes the view doesn't render at all. I'm logging out my render function, and it's only executed once when this bug happens. The times that the view is ok, render fires twice (once with a blank collection, once with a full collection)

Yep.

I'm also getting this error:
Uncaught TypeError: Cannot call method 'resolveWith' of undefined backbone.layoutmanager.js:149

Owner

tbranyen commented Jul 25, 2012

@dstibrany just pushed up some changes, please give those a shot. if its still broken, we'll have to narrow it down in such a way that doesn't clutter the commit logs and this issue's comments.

Ok, do on my old branch (the one I originally posted about) I'm having a very hard time re-creating that bug now. I say that because I still saw it happen like once or twice in like 200 page refreshes.

In my latest code, I can reproduce this pretty easily. The view that this happens on is definitely more complex than anything I previously had, though (it's like a folder-tree structure).

Let me actually make sure that the latest bug is not on my end.


Ok, I wasn't re-rendering that view when the collection was reset. That improved things, but I'm still seeing blanks like 1 out of 5 times (compared to it working 1 out 5 times when I wasn't re-rendering)

Owner

tbranyen commented Aug 3, 2012

Please re-open if the latest stable does not correct this problem.

tbranyen closed this Aug 3, 2012

the problem exists in the latest 0.7 version,
initialize: function () {
this.collection.fetch();
this.collection.on("reset", this.render, this);
},
beforeRender: function () {
console.log('before');
},
afterRender: function () {
console.log('after');
}

I see the following pattern:

before
before
after
after

and thus, getting items duplicated

Owner

tbranyen commented Oct 21, 2012

Are you sure render isn't called twice? Also nothing is duplicating here?

On Oct 20, 2012, at 6:52 PM, Aleksandr Guidrevitch notifications@github.com
wrote:

the problem exists in the latest 0.7 version,
initialize: function () {
this.collection.fetch();
this.collection.on("reset", this.render, this);
},
beforeRender: function () {
console.log('before');
},
afterRender: function () {
console.log('after');
}

I see the following pattern:

before
before
after
after


Reply to this email directly or view it on
GitHubhttps://github.com/tbranyen/backbone.layoutmanager/issues/107#issuecomment-9637726.

I do

                projects.fetch(); 
                tasks.fetch();
                app.layout.setViews({
                    "section": new Views.Layout({})
                }).render();

where

        Views.Layout = Backbone.View.extend({
            template: "search/layout",
            views: {
                "#left-sidebar": new Views.Projects({
                    collection: projects
                }),
                "#right-sidebar": new Views.Tasks({
                    collection: tasks
                })
            }
        });

where

        Views.Projects = Backbone.View.extend({
            template: 'search/projects',
            initialize: function () {
                this.collection.on("reset", this.render, this);
                this.collection.on("add", this.render, this);
            },
            beforeRender: function () {
                this.collection.each(function (model) {
                        this.insertView("ul", new Views.Project({
                            model: model
                        }));
                    }, this);
            });

And I get:

  1. Views.Project.beforeRender starts
  2. 'reset' event comes in (projects loaded from server), Views.Project.beforeRender starts again adding duplicates
  3. Views.Project.afterRrender (1) happens
  4. Views.Project.afterRender (2) happens

That looks, to me, like it's working as expected.

You're calling render() twice: once with setViews(...).render() and a
second time on the collection reset event.

On Sun, Oct 21, 2012 at 7:08 AM, Aleksandr Guidrevitch <
notifications@github.com> wrote:

I do

            projects.fetch();
            tasks.fetch();
            app.layout.setViews({
                "section": new Views.Layout({})
            }).render();

where javascript Views.Layout = Backbone.View.extend({ template: "search/layout", views: { "#left-sidebar": new Views.Projects({ collection: projects }), "#right-sidebar": new Views.Tasks({ collection: tasks }) } }); wherejavascript
Views.Project = Backbone.View.extend({
template: 'search/projects',
initialize: function () {

            this.collection.on("reset", this.render, this);


            this.collection.on("add", this.render, this);
        },
        beforeRender: function () {
            this.collection.each(function (model) {
                    this.insertView("ul", new Views.Project({
                        model: model
                    }));
                }, this);
        });

And I get:1. Views.Project.beforeRender starts2. 'reset' event comes in (projects loaded from server), Views.Project.beforeRender starts again adding duplicates3. Views.Project.afterRrender (1) happens4. Views.Project.afterRender (2) happens


Reply to this email directly or view it on GitHubhttps://github.com/tbranyen/backbone.layoutmanager/issues/107#issuecomment-9641626.

what is the correct pattern then ? how can I be sure collection is not syncing when rendering view ?

Owner

tbranyen commented Oct 21, 2012

This doesn't make sense to me. There is code inside LayoutManager explicitly to avoid this situation. We should figure this out before pushing out the new version.

tbranyen reopened this Oct 21, 2012

Owner

tbranyen commented Oct 21, 2012

@aguidrevitch What do your templates look like?

main layout:

<nav></nav><section id="content" class="container"></section>

nested layout (search/layout) :
<div class="row">
<div id="left-sidebar" class="span2"></div>
<div id="right-sidebar" class="span10"></div>
</div>

I'm trying to get you a stripped down code to reproduce the issue

Owner

tbranyen commented Oct 21, 2012

Also, @aguidrevitch is your collection empty to start with? You mention that the second beforeRender causes the duplication, but if you had no items to start with there would be no duplicates right?

Owner

tbranyen commented Oct 21, 2012

This code is confusing me very much. You have new Views.Project and you're passing it a collection the first time. Which allows this.collection.on to work inside your initialize function. However, in your beforeRender you're inserting the exact same View only this time with a model. Your initialize code should be breaking. this.collection is undefined.

'Views.Project =' should read as 'View.Projects ='. View.Project itself is

        Views.Project = Backbone.View.extend({
            template: "search/project",
            tagName: 'li',
            data: function () {
                return {
                    project: this.model
                };
            }
        });

ok, here is the code to reproduce, latest githubs bb, layoutmanager from wip branch, router.js:

define([
  // Application.
  "app"
],

function(app) {

  var Collection = Backbone.Collection.extend({
    url: '/data.js'
  });
  var collection = new Collection();

  var Item = Backbone.View.extend({
    tagName: 'li',
    initialize: function () {
        this.$el.append(this.model.id);
    }
  });
  var One = Backbone.View.extend({
    tagName: 'ul',
    initialize: function () {
        this.collection.on('reset', this.render, this);
    },
    beforeRender: function () {
        this.collection.each(function (model) {
            this.insertView(new Item({
                model: model
            }));
        }, this);
        console.log('before');
    },
    afterRender: function () {
        console.log('after');
    }
  });

  // Defining the application router, you can attach sub routers here.
  var Router = Backbone.Router.extend({
    routes: {
      "": "index"
    },

    index: function() {
        collection.fetch();
        app.useLayout("main").setViews({
            '#one': new One({
                collection: collection
            })
        }).render();
    }
  });

  return Router;

});

console output:

before 
before 
after
after
before
after

data.js:

[{ "id": 1}, { "id": 2}]

main.html:

<div>
<div id="one"></div>
<div id="two"></div>
</div>

with 0.6.6 I get:

before
before
after
after

and produces duplicates constantly.

Actually with 0.7 it looks right since afterRender is called in viewDeferred.done( ), but I still experience duplicates in my app. Trying to reproduce in the example above

Basically, if in 0.7 before is logged 3 times, everything works ok, but sometimes it gets fired only 2 times. I've added:

            initialize: function () {
                this.collection.on("reset", function () {
                    console.log('reset')
                });
                this.collection.on("reset", this.render, this);
            },

and now I get
reset
before
before

and there are duplicates. Sometimes 'before' appears 3 times and then everything is ok. Reset event is fired only once in any case, since there is only one .fetch();

one more important thing I guess - if to comment collection.fetch(); in router, or remove this.collection.on('reset', this.render, this) in view One, 'before' is still emitted twice.

Owner

tbranyen commented Oct 21, 2012

@aguidrevitch I moved the render queue logic to only happen after an afterRender. Can you give the latest wip a shot?

nothing is rendered at all, beforeRender happens once, afterRender never gets called

Owner

tbranyen commented Oct 21, 2012

@aguidrevitch you have something very wrong happening then since all the unit tests pass :-/ can you zip up your project and email me it or can you get on skype? I'd be happy to help you figure this out that doesn't involve live chatting in the github comments =)

Can you get on IRC and join #backbone-boilerplate on irc.freenode.net ?

skype: aguidrevitch

For what it's worth, this is also what's happening with my app, as per #181 - now nothing is rendered at all.

Did you guys come to a solution?

Owner

tbranyen commented Oct 22, 2012

Hey guys try the latest file?

Still nothing for me - app. Am I doing something incorrectly here?

Owner

tbranyen commented Oct 22, 2012

@engram-design I pulled down your code and had to comment out all the auth stuff, it worked fine for me, except for getting duplicate views. thats what you're seeing too right?

Should work with the auth stuff (just a dummy login thing, as I thought switching layouts might have something to do with the issue). After commenting out said auth info, I'm seeing duplicate views. At least they're rendering...

Sorry to hijack the thread :)

Owner

tbranyen commented Oct 22, 2012

@engram-design does the last commit work for you? /cc @aguidrevitch

Wohoo! You got it champ :)

tbranyen closed this Oct 22, 2012

works like a charm. No duplicates so far

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment