redress
redress

Reputation: 1439

Backbone: Re-render existing model in new DOM element

I am using a collection view to render my array of model views. I have added a method that removes a single model view from the existing collection view, and attempts to re-render it in a new el: element.

I use collection.get(this.model) to save the model to a variable, I add that variable to my new collection which is the model of a new collection view associated with a new DOM element, and I re-use the same collection view render method. When I console.log() the new collection, I see the model that I picked from the old collection, but it's not rendering on the page.

<script>

    (function($){

    //---------SINGLE ENTRY MODEL----------
            var Entry = Backbone.Model.extend({
                defaults: function(){
                    return{
                        word: '',
                        definition: ''
                    }
                }
            });

        //------------ENTRY MODEL COLLECTION------------
            var EntryList = Backbone.Collection.extend({
                model: Entry
            });

        //-----INSTANCIATE COLLECTION----
        var dictionary = new EntryList();
        var saved = new EntryList();

        //-----SINGLE ENTRY VIEW------
        var EntryView = Backbone.View.extend({
            model: new Entry(),
            tagName:'div',

            events:{
                'click .edit': 'edit',
                'click .delete': 'delete',
                'keypress .definition': 'updateOnEnter',
                'click .save': 'save'
            },

            delete: function(ev){
                ev.preventDefault;
                dictionary.remove(this.model);

            },

            edit: function(ev){
                ev.preventDefault;
                this.$('.definition').attr('contenteditable', true).focus();

            },
            //method that adds existing model to new collection
            save: function(ev){
                ev.preventDefault;
                var savedEntry = dictionary.get(this.model);
                dictionary.remove(this.model);
                saved.add(savedEntry);
                console.log(savedEntry.toJSON());

            },
            close: function(){
                var definition = this.$('.definition').text();
                this.model.set('definition', definition);
                this.$('.definition').attr('contenteditable', false).blur();

            },

            updateOnEnter: function(ev){
                if(ev.which == 13){
                    this.close();
                }
            },

            initialize: function(){
                this.template = _.template($("#dictionary_template").html());

            },

            render: function(){
                this.$el.html(this.template(this.model.toJSON()));
                return this;
            }
        });

        //--------------DICTIONARY VIEW------------
        var DictionaryView = Backbone.View.extend({
            model: dictionary,
            el: $('#entries'),

            initialize: function(){
                this.model.on('add', this.render, this);
                this.model.on('remove', this.render, this);

            },

            render: function(){
                var self = this;
                self.$el.html('');
                _.each(this.model.toArray(), function(entry, i){
                    self.$el.append((new EntryView({model: entry})).render().$el);
                });

                return this;
            }
        });

        //---------SAVED ENTRY VIEW-----------
        var SavedView = Backbone.View.extend({
            model: saved,
            el: $('#saved'),

            initialize: function(){
                this.model.on('save', this.savedRender, this);
            },
            //method that renders new collection view with different el: 
            savedRender: function(){
                var self = this;
                self.$el.html('');
                _.each(this.model.toArray(), function(saved, i){
                    self.$el.append((new EntryView({model: savedEntry})).render().$el);
                });

                return this;
            }
        });


        //-------BINDING DATA ENTRY TO NEW MODEL VIEW-------
        $(document).ready(function(){
            $('#new-entry').submit(function(ev){
                var entry = new Entry({word: $('#word').val(), definition: $('#definition').val()       });

                dictionary.add(entry);
                dictionary.comparator = 'word';

                console.log(dictionary.toJSON());

                $('.form-group').children('input').val('');

                return false;
            });
            var appView = new DictionaryView();
        });


        //--------------ROUTER----------------
        var Router =  Backbone.Router.extend({
            routes:{
                '':'home' 
            }
        });
        var router = new Router();
        router.on('route:home', function(){
            console.log('router home');
        });
        Backbone.history.start();
    })(jQuery); 

    </script>

Upvotes: 0

Views: 533

Answers (1)

76484
76484

Reputation: 8993

There are a number of problems here.

First, you do not have an instance of SavedView. The var SavedView = Backbone.View.extend(...); statement is just defining the SavedView class. In order to have a living instance of this class, you must initialize one with the new operator. You will need a line like the following somewhere in your code (a good place would be at the end of the jQuery ready handler):

var saved_view = new SavedView();

Next, we will investigate the save method of the EntryView class. The var savedEntry = dictionary.get(this.model); statement is completely unnecessary because we know that dictionary.get(this.model) will return this.model - which we obviously already have an instance of. So we can remove the clutter from this method and be left with the following:

ev.preventDefault;
saved.add(this.model);
dictionary.remove(this.model); 

However, we are still not at our destination. If we turn our attention to the SavedView class definition, we see that it is binding its render method to the 'save' event on its collection, the saved object. Its not the 'save' event we should be binding to, but rather the 'add' event - as that is what will be triggered when we add models to saved:

this.model.on('add', this.savedRender, this);

If we test our code now we should get scolded with a reference error on savedEntry within SavedView.savedRender. It looks like this is a typo and what was intended was `saved'. (You will notice below that in addition to correcting the typo, I have also removed a set of parentheses from this expression that served no function save for making the code less readable):

self.$el.append(new EntryView({ model: saved }).render().$el);

EDIT:

In response to your follow-up question about the saved variable inside the SavedView.savedRender method:

The saved object in this case is a single Entry model. The reason for your confusion is that we are re-using the variable name "saved". Within the _.each callback we have defined the first parameter to be called "saved"; this "saved" is local to the callback and is not related to the EntryList collection defined previously. Within our callback, saved is an element of the saved collection (yikes!) - which is a lesson in why variable naming is important.

As I proceeded to change the name of "saved" in the savedRender method, I noticed a few other refactorings that were screaming to be made. I have listed my refactorings below:

  1. A purpose of using Backbone (http://backbonejs.org/) is to give us access to convenient helpers for objects (models) and arrays (collections). Backbone collections have an each method we can make use of instead of passing our collection to Underscore's (http://underscorejs.org/) each.

  2. As stated above, saved is a terrible name for our each callback parameter because it conflicts conceptually with the name of the collection. Because saved is a collection of Entry models, "entry" is a much more suitable name.

  3. Backbone allows us to pass the context to our each callback that will be our this within that callback. This allows us to skip the step of caching our this in the self variable.

My refactored savedRender becomes:

savedRender: function () {    
    this.$el.empty();
    this.model.each(function (entry) {
        this.$el.append(new EntryView({ model: entry }).render().$el);
    }, this);    

    return this;
}

Upvotes: 2

Related Questions