Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Getting duplicate list item views #107

Closed
dstibrany opened this Issue · 44 comments

5 participants

@dstibrany

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?

@dstibrany

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.

@tbranyen
Owner

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.

@dstibrany

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?

@tbranyen
Owner

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

@dstibrany

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.

@tbranyen
Owner

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.

@dstibrany

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

@tbranyen
Owner

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

@dstibrany

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()})
     ]
});
@dstibrany

Your latest commit 60fd982 fixed reverse order of views.

@tbranyen
Owner

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)

@dstibrany

Yep.

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

@tbranyen
Owner

@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.

@dstibrany

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)

@tbranyen
Owner

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

@tbranyen tbranyen closed this
@aguidrevitch

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

@tbranyen
Owner
@aguidrevitch

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

@andornaut
@aguidrevitch

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

@tbranyen
Owner

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 tbranyen reopened this
@tbranyen
Owner

@aguidrevitch What do your templates look like?

@aguidrevitch

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>

@aguidrevitch

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

@tbranyen
Owner

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?

@tbranyen
Owner

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.

@aguidrevitch

'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
                };
            }
        });
@aguidrevitch

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>

@aguidrevitch

with 0.6.6 I get:

before
before
after
after

and produces duplicates constantly.

@aguidrevitch

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

@aguidrevitch

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

@aguidrevitch

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.

@tbranyen
Owner

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

@aguidrevitch

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

@tbranyen
Owner

@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 ?

@aguidrevitch

skype: aguidrevitch

@engram-design

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?

@tbranyen
Owner

Hey guys try the latest file?

@engram-design

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

@tbranyen
Owner

@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?

@engram-design

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 :)

@tbranyen
Owner

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

@engram-design

Wohoo! You got it champ :)

@tbranyen tbranyen closed this
@aguidrevitch

works like a charm. No duplicates so far

@spoonkojo spoonkojo referenced this issue from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.