2

Here's the fiddle:

http://jsfiddle.net/QhQ8D/10/

Code is down below. Making a chat app and need a sorted, connected user list. Figured Collection with a comparator on name hooked to a CompositeView should do it. Am I doing something wrong in the implementation here?

HTML:

    <div>Enter user name and hit add user to build your list</div>
    <div id="divadduser">
        <input id="inputusername"/>
        <input id="buttonadduser" type="button" value="add user"/>
    </div>

    <div id="divusers"></div>

JAVASCRIPT:

    var nextAvailableUserId = 0;

    //Define a region to show a list of users 
    var userListRegion = new Backbone.Marionette.Region({ el: '#divusers'});

    var ConnectedUserModel = Backbone.Model.extend();

    //Make a user item view
    var UserView = Backbone.Marionette.ItemView.extend({
        template: Handlebars.compile(
            '<a id="{{id}}">{{name}}</a>'        
        ),
        tagName: "li",   
    });

    //Define a user collection
    var UserCollection = Backbone.Collection.extend({
        model: ConnectedUserModel,
        comparator: "name"
    });

    //Make a user collection instance
    var collConUsers = new UserCollection();                   

    //Define a composite user list view 
    var UserListView = Backbone.Marionette.CompositeView.extend({
        template: Handlebars.compile(
           '<ul id="ulusers"></ul>'
        ),

        itemView: UserView,
        itemViewContainer: 'ul',

        collectionEvents: {
            "add": "doSort"
        },

        doSort: function () {
            this.collection.trigger('reset');
        }
    });

    //Make a composite user list view instance
    var view = new UserListView({
        collection: collConUsers                        
    });

    //Show the view
    userListRegion.show(view);

    //Handle add user button click
    $('#buttonadduser').click(function () {    
        var uName = $("#inputusername").val();
        if (uName.length > 0) {
            nextAvailableUserId += 1;
            collConUsers.add([{ id: nextAvailableUserId, name: uName }]);        
            $("#inputusername").val('');
        }
    });

UPDATE:

I'm leaving this marked answered but the solution below is not optimal. In essence it overrides how CollectionView or CompositeView appendHtml. Technically this helps the issue described in this thread but it seems to create other problems. In my case I also have a JQuery filter on my user list (like this - http://kilianvalkhof.com/uploads/listfilter/). This override breaks that filter. Not sure why just yet. If I discover why I'll update this post.

After a few days on this I've found no reliable BB marionette way to sort on add AND filter on each keystroke a user list without rendering duplicate models. If I do I'll update. But I just think the benefit of the collection/view bond here is not fully realized without this piece. I did this with minimal difficulty in AS3.

I think the real answer is that views should accurately represent the state of a model. If a model has had an addition and been sorted, a view should reflect that. Duplicate model rendering feels like a bug.

UPDATE:

Learning as I go here. You have to specify in the override precisely where you want your content to go. As such, my 'ul' itemViewContainer is no longer relevant. So the final answer for me was:

  1. Ditch itemViewContainer
  2. Override appendHtml to tell my CompositeView explicitly where my users ul was and also to insert users by index according to sort order
  3. Ditch my "add" collectionEvent and handler

http://jsfiddle.net/QhQ8D/29/

Whew...

Robert
  • 828
  • 2
  • 13
  • 28

1 Answers1

3

Your doSort() function, triggering reset is what does the trick. If you comment that out, it only produces one copy of the view.

Use a comparator() function to sort the model, don't reset the list every time it is added.

UPDATE

More ideas on sorting as models are added are explained here:

The best way to sort a collection in a CompositeView

I used the appendHTML extension example in this post successfully in my own app (the first response to the question).

Community
  • 1
  • 1
dthree
  • 19,847
  • 14
  • 77
  • 106
  • Commenting that line out eliminates the sort. One copy of the view is produced but the names entered are not sorted on add. I think that reset is what tells the view to re-render the collection in the new sorted order. – Robert Oct 02 '13 at 19:12
  • True that. Check out the article I linked there - extending backbone with a custom appendHTML function will make your compositeViews automatically sort. – dthree Oct 02 '13 at 19:13
  • Actually the appendHTML extension approach does solve the extra model render problem but it does cause a different problem for me. It breaks a nice JQuery filter that I had on the user list. Trying to figure out why. My filter was the approach used here - http://kilianvalkhof.com/uploads/listfilter/. Here was the explanation of the filter - http://kilianvalkhof.com/2010/javascript/how-to-build-a-fast-simple-list-filter-with-jquery/. I'll have to try to figure out where the break is and try to distill into another post. – Robert Oct 02 '13 at 21:03
  • Yeah I'm gonna go ahead and say that appendHTML extension method is not a good idea. Something doesn't seem right about it. Yes it solves one problem but causes others. Not sure it's a good trade. I think sorting and filtering in backbone and marionette views just aren't there yet. I've been at this a few days and can't come to a reasonable solution - one that lets me keep a user list sorted AND filtered on each key stroke AND doesn't duplicate models. I have seen no solution that offers this. I've done this in ActionScript. Can't match the functionality in BB or marionette though. – Robert Oct 02 '13 at 21:41
  • This override changes the html output. It seems it doesn't properly account for my CompositeView's itemViewContainer - 'ul'. It causes the 'li' s to be rendered and their container, the '
      ', to be tacked on afterward. Need the 'li' s inside the itemViewContainer - '
        '.
        – Robert Oct 02 '13 at 23:34
      • That's interesting. Its really funny you are bringing this up, as I have literally been tackling the same problem today and yesterday. I just got a custom ComboBox (similar to ExtJS's combobox) working in full, with automatic sorting and filtering of a model. – dthree Oct 02 '13 at 23:49
      • You can see a question I posted on this here: http://stackoverflow.com/questions/18604233/marionette-js-collectionview-only-render-specific-models - David Sulc's answer and solution did it for me, which I elaborate on in an update of my question. – dthree Oct 02 '13 at 23:50
      • Ok, I sort of get what that override is now. Lack of understanding on my part. It's for granular control over putting things exactly where you want them inside the CompositeView. As such, itemViewContainer is no longer relevant. You're going to direct your output where you want by name ("#ulusers" in my case). I wanted li items inside a ul. Just had to arrange it that way explicitly inside the override - http://jsfiddle.net/QhQ8D/28/ – Robert Oct 03 '13 at 01:08
      • David Sulc's solution is very nice. Conceptually I do prefer the notion of filtering at the collection level. But the visual turns out slightly less smooth than the key by key JQuery approach here - http://kilianvalkhof.com/uploads/listfilter/. I now have that approach working in my user list. I may still try David's approach yet, particularly if the current approach doesn't perform well. – Robert Oct 03 '13 at 01:21
      • Yeah do whatever works for you. I like David Sulc's idea just because it's quite DRY - I can easily call and reuse it anywhere. – dthree Oct 03 '13 at 01:45
      • I agree with that. I may get there yet ;) – Robert Oct 03 '13 at 02:11