View not initializing in Backbone.js

70 Views Asked by At

Ive created a simple backbone app that gets data from MySQL database about users to display in a view called LeaderBoardView. Below is the HTML code for the view,

<body>
<div id="container"></div>
<h1>Leaderboard</h1>
<table class="table" id="modtable">
  <tr>
     <th>Username</th>
     <th>Level</th>
  </tr>
 </table>
 <div id="bbcontent"></div>

Im trying to get data and populate inside the div with bbcontent as the id. Below is my Backbone model, collection and view,

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"> 
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-  
min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.2.3/backbone-min.js"> 
</script>


<script language="javascript">
  $(document).ready(function() {
     alert("heyyyyyy")
     //model
     var User = Backbone.Model.extend({
        idAttribute: "userId",
        defaults: {
           username: null,
           userLevel: null
        }
     });

     //collection
     var Users = Backbone.Collection.extend({
        model: User,
        url: "/CW2/ASSWDCW2/cw2app/index.php/Leaderboard/leaderboard",
     });

     var usersC = new Users();

     var LeaderboardDeetsView = Backbone.View.extend({
        model: usersC,
        el: $('#bbcontent'),
        intialize: function() {
           alert("asndasnxdjksa")
           usersC.fetch({
              async: false
           })
           this.render()
        },
        render: function() {
           var self = this;
           usersC.each(function(c) {
              var block = "<div class='name'><h1>" + c.get('username') + "</h1></div>"
              self.$el.append(block)
           })
        }
     })

     var leaderboardDeetsView = new LeaderboardDeetsView();
  });

Problem with this code : The LeaderboardDeetsView isn't being called hence the collection fetch function inside the initialize function of the LeaderboardDeetsView isn't being called.How can I correct my code? Please help

1

There are 1 best solutions below

0
Julian On

I see eleven issues with your code. The first two prevent your code from working as intended:

  1. As DSDmark pointed out, there is a typo in your code: intialize instead of initialize. The method will not run for this reason.
  2. Inside the initialize method, you are attempting to fetch the users synchronously, in order to render them immediately. Alas, there is no such thing as a synchronous request, so your view will be rendering an empty collection. In good Backbone style, you need to listen for events so you know when is the right time to render:
initialize: function() {
   // { async: false } does not do anything, so we may as well 
   // remove it.
   usersC.fetch()
   // You can still render immediately, in case the users have
   // already been fetched before the view was constructed,
   this.render()
   // ... but in any case, you probably want to re-render whenever
   // the data change (such as when the above request completes).
   this.listenTo(usersC, 'update', this.render);
},

The next six issues are missed opportunities to follow best practices. These do not currently break your code, but they very well might in the future:

  1. You are setting usersC as the model of LeaderboardDeetsView, but it is a collection. Views have both a model and a collection property, so you should use each for its proper purpose.
  2. You are setting the model (which should be the collection) on the prototype. While this works in principle, you cannot use this mechanism to have multiple instances of LeaderboardDeetsView that each present a different list of users (since they all share the same prototype). For this reason, the View constructor accepts an options object with model and collection properties, so you can give each view its own unique model:
var LeaderboardDeetsView = Backbone.View.extend({
   el: $('#bbcontent'),
   initialize: function() {
      this.collection.fetch()
      this.render()
      this.listenTo(this.collection, 'update', this.render);
   },
   render: function() {
      var self = this;
      this.collection.each(function(c) {
         var block = "<div class='name'><h1>" + c.get('username') + "</h1></div>"
         self.$el.append(block)
      })
   }
})

var leaderboardDeetsView = new LeaderboardDeetsView({
   collection: usersC,
});
  1. In several places, you are not finishing your statements with a semicolon (;). JavaScript will let you get away with this most of the time, but not always. Train yourself to be strict with this and save yourself some unpleasant and confusing surprises down the line!
  2. In the MVC paradigm, a view should not decide when to fetch data, unless it is in response to a user action (in which case the view is taking the role of a controller). In your case, since you want to fetch the data immediately after starting the application, the call to fetch belongs outside of the view.
  3. In the class definition of LeaderboardDeetsView, you set the el to an already resolved jQuery instance. It works fine in this case, but in the general case, the element with the given selector might not exist yet. Set the el to just a selector string instead, and the view will perform this lookup for you automatically when the view is constructed.
  4. By convention, the render method of a view should return this so you can continue chaining methods after it. The same is true of most other methods that do not already return some other value. Taking into account all issues so far, your code should now look like this:
//model
var User = Backbone.Model.extend({
   idAttribute: "userId",
   defaults: {
      username: null,
      userLevel: null
   }
});

//collection
var Users = Backbone.Collection.extend({
   model: User,
   url: "/CW2/ASSWDCW2/cw2app/index.php/Leaderboard/leaderboard",
});

var usersC = new Users();
// Fetch the collection right after construction.
usersC.fetch();

var LeaderboardDeetsView = Backbone.View.extend({
   // Selector string will be jQuery-wrapped automatically.
   el: '#bbcontent',
   initialize: function() {
      // We can chain listenTo after render because of "return this".
      this.render().listenTo(this.collection, 'update', this.render);
   },
   render: function() {
      var self = this;
      this.collection.each(function(c) {
         var block = "<div class='name'><h1>" + c.get('username') + "</h1></div>";
         self.$el.append(block);
      });
      // This last line enables chaining!
      return this;
   }
});

var leaderboardDeetsView = new LeaderboardDeetsView({
   collection: usersC,
});

The last three issues are missed opportunities to benefit from the latest and greatest of the libraries that are available to you:

  1. You are using highly outdated versions of jQuery, Underscore and Backbone. These are all very stable libraries, so you can benefit from over seven years of bugfixes, performance boosts and improved compatibility with modern browsers, all without changing a single character in your code!
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.3/jquery.min.js"> 
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.13.6/underscore-  
min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.4.1/backbone-min.js"> 
</script>
  1. Rendering the same thing for every model in a collection is something that nearly every web application needs to do. Of course there are libraries that streamline this job for you. Below, I show how you could rewrite LeaderboardDeetsView using backbone-fractal, a small library that I wrote. Alternatively, you could use the CollectionView from Marionette (but the syntax is different in that case). This makes your code more modular, easier to understand and easier to test and maintain.
// A simple, dedicated view for a single entry in the leaderboard.
var UserView = Backbone.View.extend({
   className: 'name',
   initialize: function() { this.render(); },
   render: function() {
      this.$el.html('<h1>' + this.model.get('username') + '</h1>');
      return this;
   }
});

// A simple view for the entire leaderboard, all automatic.
var LeaderboardDeetsView = BackboneFractal.CollectionView.extend({
   el: '#bbcontent',
   subview: UserView,
   initialize: function() {
      this.initItems().render().initCollectionEvents();
   }
});
  1. Use templates to generate HTML code, rather than concatenating strings with hand-written JavaScript code. This makes the code responsible for generating the HTML much easier to read and edit. If you want to keep it cheap, you can use the built-in template function from Underscore. If you want to take your templates more seriously, you can also use a dedicated templating library such as Handlebars or Wontache. Below, I demonstrate how Underscore's _.template would work for the UserView from the previous point:
var UserView = Backbone.View.extend({
   className: 'name',
   // The template: a declarative description of the HTML you want to
   // generate.
   template: _.template('<h1><%- username %></h1>'),
   initialize: function() { this.render(); },
   render: function() {
      // Using the template. Conventional notation.
      this.$el.html(this.template(this.model.toJSON()));
      return this;
   }
});

Here is a final version of your code, with all of the above points implemented. Doesn't it look sleek, concise and modular?

var User = Backbone.Model.extend({
   idAttribute: "userId",
   defaults: {
      username: null,
      userLevel: null
   }
});

var Users = Backbone.Collection.extend({
   model: User,
   url: "/CW2/ASSWDCW2/cw2app/index.php/Leaderboard/leaderboard",
});

var usersC = new Users();
usersC.fetch();

var UserView = Backbone.View.extend({
   className: 'name',
   template: _.template('<h1><%- username %></h1>'),
   initialize: function() { this.render(); },
   render: function() {
      this.$el.html(this.template(this.model.toJSON()));
      return this;
   }
});

var LeaderboardDeetsView = BackboneFractal.CollectionView.extend({
   el: '#bbcontent',
   subview: UserView,
   initialize: function() {
      this.initItems().render().initCollectionEvents();
   }
});

var leaderboardDeetsView = new LeaderboardDeetsView({
   collection: usersC,
});