0

I inserted three console.log into the following code, which is from the tutorial Your First Backbone.js App.

My question is: why console.log(this.el) shows the element's innerHtml is already "rendered" but the console.log('render runs!') message prints after it?

Which function of the View gets executed first? initialize or render?

$(function(){

    // Create a model for the services
    var Service = Backbone.Model.extend({

        // Will contain three attributes.
        // These are their default values

        defaults:{
            title: 'My service',
            price: 100,
            checked: false
        },

        // Helper function for checking/unchecking a service
        toggle: function(){
            this.set('checked', !this.get('checked'));
        }
    });


    // Create a collection of services
    var ServiceList = Backbone.Collection.extend({

        // Will hold objects of the Service model
        model: Service,

        // Return an array only with the checked services
        getChecked: function(){
            return this.where({checked:true});
        }
    });

    // Prefill the collection with a number of services.
    var services = new ServiceList([
        new Service({ title: 'web development', price: 200}),
        new Service({ title: 'web design', price: 250}),
        new Service({ title: 'photography', price: 100}),
        new Service({ title: 'coffee drinking', price: 10})
        // Add more here
    ]);

    // This view turns a Service model into HTML
    var ServiceView = Backbone.View.extend({
        tagName: 'div',

        events:{
            'click': 'toggleService'
        },

        initialize: function(){

            // Set up event listeners. The change backbone event
            // is raised when a property changes (like the checked field)
            console.log(this);
            console.log(this.el);

            this.listenTo(this.model, 'change', this.render);
        },

        render: function(){

            // Create the HTML
            console.log("render runs!");

            this.$el.html('<input type="checkbox" value="1" name="' + this.model.get('title') + '" /> ' + this.model.get('title') + '<span>$' + this.model.get('price') + '</span>');
            this.$('input').prop('checked', this.model.get('checked'));

            // Returning the object is a good practice
            // that makes chaining possible
            return this;
        },

        toggleService: function(){
            this.model.toggle();
        }
    });

    // The main view of the application
    var App = Backbone.View.extend({

        // Base the view on an existing element
        el: $('#main'),

        initialize: function(){

            // Cache these selectors
            this.total = $('#total span');
            this.list = $('#services');


            // Listen for the change event on the collection.
            // This is equivalent to listening on every one of the 
            // service objects in the collection.
            this.listenTo(services, 'change', this.render);


            // Create views for every one of the services in the
            // collection and add them to the page

            services.each(function(service){

                var view = new ServiceView({ model: service });
                this.list.append(view.render().el);

            }, this);   // "this" is the context in the callback
        },

        render: function(){

            // Calculate the total order amount by agregating
            // the prices of only the checked elements

            var total = 0;

            _.each(services.getChecked(), function(elem){
                total += elem.get('price');
            });

            // Update the total price
            this.total.text('$'+total);

            return this;

        }

    });

    new App();

});

Output from the console is as follows:

child {cid: "view7", model: child, $el: init[1], el: div}
<div>
<input type="checkbox" value="1" name="web development"> web development
<span>$200</span></div>
render runs!
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
Shawn Chen
  • 1,017
  • 2
  • 13
  • 24

1 Answers1

1

initialize is always called first as it's inside the default Backbone's view constructor.

render is called whenever you manually call it.

services.each(function(service) {

    // ServiceView 'initialize' is called here.
    var view = new ServiceView({ model: service });
    // ServiceView 'render' is called here.
    this.list.append(view.render().el);

}, this);

Why the console shows the el before rendering?

In fact, the console doesn't show the element before rendering, but it is evaluated when you check it in the console.

Here's a simple example:

var myObject = {};

console.log(myObject);

myObject.test = "value";

If you had to guess, you'd say that an empty object is logged and you wouldn't be totally wrong.

Logged object being evaluated

The little blue ! says:

Object value at left was snapshotted when logged, value below was evaluated just now.

As mentioned by "mu is too short",

the console contains live references and doesn't copy anything. So when you get to the console to look at this.el, it shows you what this.el is right now rather than what it was when console.log(this.el) was evaluated.

Community
  • 1
  • 1
Emile Bergeron
  • 17,074
  • 5
  • 83
  • 129
  • but inside the initialize function, "this.el" is already rendered to contain stuff, which is produced inside the render function. this doesn't make sense. – Shawn Chen Sep 20 '16 at 02:45
  • @ShawnChen check the second part of my answer which explains how the console try to help by evaluating the logged object later. At first, `this.el` only contains the empty `div` mentioned by `tagName`, but when the console re-evaluate the element, it shows its latest state. – Emile Bergeron Sep 20 '16 at 02:49
  • Note that `HTMLElement` (which `this.el` is) are objects and not strings. – Emile Bergeron Sep 20 '16 at 02:50
  • 1
    @ShawnChen The console contains [live references](http://stackoverflow.com/a/11463190/479863), it doesn't copy anything. So when you get to the console to look at `this.el`, it shows you want `this.el` is right now rather than what it was when you said `console.log(this.el)`. – mu is too short Sep 20 '16 at 02:50
  • 1
    Thank you so much! You let me understand how console.log works. It is really helpful! – Shawn Chen Sep 20 '16 at 03:39
  • @ShawnChen It's a pleasure to help, welcome on Stack Overflow! – Emile Bergeron Sep 20 '16 at 03:48