Search code examples
javascriptbackbone.jsmodel-view-controllerbackbone-views

How to separate view code into model and controller in Backbone.js


I have my code, its a basic TODO list which just has the below lines of code.

The issue I am facing is that almost everything is crowded in the view itself. There is hardly any lines of code in the model or the collection, and also, there no controller.

This is negating the MVC in backbone, which i am trying to implement. Is there any way in which I can remove the code from the view and place it into any of the other modules?

(function($){
Backbone.sync = function(method, model, success, error){
    success();
}

var Item = Backbone.Model.extend({});
var List = Backbone.Collection.extend({
    model: Item
});

var ItemView = Backbone.View.extend({
    tagName: 'div', // name of tag to be created 
    events: {
        'click span.delete': 'remove'
    }, 
    initialize: function(){
        _.bindAll(this, 'render', 'unrender', 'remove'); // every function that uses 'this' as the current object should be in here
        this.model.bind('change', this.render);
        this.model.bind('remove', this.unrender);
    },
    render: function(){
        $(this.el).html("<span style='margin-left: 20px;'></span>"+this.model.get('part')+'<span class="delete" style="cursor:pointer; color:red; font-family:sans-serif;">[delete]</span>');
        return this; // for chainable calls, like .render().el
    },
    unrender: function(){
        this.model.destroy();
    }
});


var ListView = Backbone.View.extend({
    el: $('body'), // attaches `this.el` to an existing element.
    events: {
        'click button#add': 'addItem'
    },
    initialize: function(){
        _.bindAll(this, 'render', 'addItem'); // fixes loss of context for 'this' within methods

        this.collection = new List();
        this.collection.bind('add', this.appendItem); // collection event binder
        this.render(); // not all views are self-rendering. This one is.
    },
    render: function(){
        var self = this;
        $(this.el).append("<body style='margin: 0; padding: 0;' id='body'><div  style='margin: 20px;'><h1>TODO list in plain JS and JQuery</h1><input type='text' id='taskBox' autofocus/>");
        $(this.el).append("<span id='appendToSpan'></span>");
        $(this.el).append("<button style='margin-left: 20px;' id='add'>Add list item</button>");
        _(this.collection.models).each(function(item){ // in case collection is not empty
            self.appendItem(item);
        }, this);
    },
    addItem: function(){
        var item = new Item();
        var val = $(this.el).find('input#taskBox').val();
        $(this.el).find('input#taskBox').val('');
        //this.model.set({'part': val});

        item.set({'part': val});

        this.collection.add(item);
    },
    appendItem: function(item){
        var itemView = new ItemView({
            model: item
        });
        $('span#appendToSpan', this.el).append(itemView.render().el);
    }
});

var listView = new ListView();
})(jQuery);
  <!DOCTYPE html>
  <html>
  <head>
    <meta charset="utf-8">
    <title>TODO App</title>
  </head>
  <body>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
    <script src="http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
    <script src="http://documentcloud.github.com/underscore/underscore-min.js"></script>
    <script src="http://documentcloud.github.io/backbone/backbone-min.js"></script>

    <script src="views/demo1View.js" type="text/javascript"></script>
    
  </body>
  </html>


Solution

  • Backbonejs is not an MVC Framework. It's rather more MVP ( P - presenter) framework. In other words you could reside your logic into the View that plays role of Controller in true MVC frameworks.

    Just try to separate responsibility of components and keep it as simple as possible.

    I would recommend the following refactoring of your code:

    (function($){
    Backbone.sync = function(method, model, success, error){
        success();
    }
    
    var Item = Backbone.Model.extend({});
    var List = Backbone.Collection.extend({
        model: Item
    });
    
    var ItemView = Backbone.View.extend({
    
        events: {
            'click .js-delete': 'remove'
        },
     
        template: _.template($('#itemViewTemplate').html()),
      
        initialize: function(){
            this.listenTo(this.model, 'change', this.render);
            this.listenTo(this.model, 'remove', this.unrender);
        },
        
        render: function() {
        	this.$el.html(this.template(this.model.toJSON()));
            return this;
        },
        
        unrender: function(){
            this.$el.remove();
        }
    });
    
    
    var ListView = Backbone.View.extend({  
    	
        events: {
           'click .js-addItem': 'addItem'
        },
                
        initialize: function(){
            this.listenTo(this.collection, 'add', this.appendItem);
        },
        
        render: function(){
            this.collection.each(this.appendItem, this);
            return this;
        },
        
        addItem: function(){
            var $input = this.$('input#taskBox')
            var val = $input.val();
          
            this.collection.add({part: val});
            
            // clear input                        
            $input.val('');            
        },
    
        appendItem: function(item){
            var itemView = new ItemView({
                model: item,
                template: this.template
            });
            
            this.$('#appendToSpan').append(itemView.render().el);
        }
    });
    	
    // Initialize widget and empty collection
    var listView = new ListView({
        el: 'body',
        collection: new List()
    });
    
    // Render collection
    listView.render();
    
    })(jQuery);
    <!DOCTYPE html>
      <html>
      <head>
        <meta charset="utf-8">
        <title>TODO App</title>
        <style>
          body {
            margin: 0;
            padding: 0;
          }
         
          .container {
            margin: 20px;
          }
    
          .addButton {
             margin-left: 20px;  
             margin-top: 10px;
          }
    
          .item-name {
             margin-left: 20px;
          }
    
          .item-remove {
             cursor:pointer;
             color:red;
             font-family:sans-serif;
          }
    
        </style>
      </head>
      <body>
        <div class="container">
        <h1>TODO list in plain JS and JQuery</h1>
        <input type='text' id='taskBox' autofocus/>
        <div id='appendToSpan'></div>
        <button class="addButton js-addItem">Add list item</button>
      	
        <!-- Item template -->
        <script type="text/template" id="itemViewTemplate">
           <span class="item-name"><%= part %></span>
           <span class="js-delete item-remove">[delete]</span>
        </script>
    
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
        <script src="http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
        <script src="http://documentcloud.github.com/underscore/underscore-min.js"></script>
        <script src="http://documentcloud.github.io/backbone/backbone-min.js"></script>
        
      </body>
      </html>