Search code examples
javascriptjquerybackbone.jsbackbone-viewsbackbone-events

Backbone model objects.... where are they stored so that the DOM can interact with them?


I just realized that I have no idea what the heck I am doing when it comes to backbone. I came to this realization when trying to figure out a cogent strategy for removing the view's event listeners on the model. Then I asked "well, where is the model anyways now that the view has been rendered to the DOM?" and then I asked "how is this model object that I created inside a function body, and is therefore out of scope now that I have rendered the view to the DOM, maintaining state?" AHHHHHHHH!!!!!!!!!

Ex.

View Constructor

Timeclock.Views.JobNewView = Backbone.View.extend({
  template: JST['jobs/_form'],
  events:{
   'blur #job_form :input':'assignValue'
  },
  initialize: function(options){
    this.listenTo(this.model, 'failed-request', this.failedLocationRequest);
    this.listenTo(this.model, 'updated-location', this.updatedLocation);
    this.listenTo(this.model, 'sync', this.renderJobView); 
    this.listenTo(this.model, 'invalid', this.displayModelErrors);
    this.listenTo($(window), 'hashchange', this.clearListeners);
  },
  render: function(){
    this.$el.html(this.template({attributes: this.model.attributes}));
    this.$el.find('#address_fields').listenForAutoFill();
    return this;
  },
  assignValue: function(e){
    var $field = $(e.currentTarget)
    var attr_name = $field.attr('name');
    var value = $field.val();
    this.model.set(attr_name, value);
  }...
});

Function rendering view to the DOM

renderCollaboratingView: function(e){
  var job = this.model;
  var $row = $(e.currentTarget);
  job.set({customer_id: $row.data('id')});
  var model_view = new this.ViewConstructor({model: job});
  $container.html(model_view.render().el);
}

So how is the model that I am passing to the view object persisted so that the DOM interactions can set attribute values on the underlying model object?

I understand that backbone views are just a wrapper to declaratively write DOM listeners but how are DOM events acting on the underlying model object in the example above? As soon as the renderCollaboratingView() function has exited how is the model that I passed to the view still being interacted with?

I can think of two ways:

1) The model object is bound to the DOM through a jquery object. All the event listeners that I declare in my view all know where the underlying model object lives on the jquery object(the 'model' attribute?).

2) Backbone is creating some object namespace that the view knows about where it stores models and collections that back the DOM. I have a feeling it's #1 but who knows.

Once again, I got here because I was trying to understand why I need to remove the listeners on the model that I passed into view in the first place. If backbone views are really just jquery objects then aren't jquery listeners removed from DOM elements when the element backing the jquery object is removed from the DOM? Do I only need to remove the listeners if I am going to not destroy the view entirely and save it for later use?

Any help that can be given would be greatly apprecaited. Having an existential crisis.

Thanks


Solution

  • So how is the model that I am passing to the view object persisted so that the DOM interactions can set attribute values on the underlying model object?

    Backbone Models and Views are simply Javascript objects that live in-memory in the scope of the page (like any other Javascript). If you were to do ...

    var name = 'Peter';
    var person = new Backbone.Model({ name: 'Peter' });
    var view = new Backbone.View({ model: person } );
    

    ... then name, person, and view are all just objects in memory. They have no relation to jQuery; they have no relation to the DOM. The View happens to be able to create DOM elements if you implement render(), but even then those elements don't ever have to ever be attached to the page's live DOM at all.

    ... how are DOM events acting on the underlying model object in the example above? As soon as the renderCollaboratingView() function has exited how is the model that I passed to the view still being interacted with?

    Based on the code you've shown, the model isn't being interacted with directly. Your events hash ...

    events:{
        'blur #job_form :input':'assignValue'
    },
    

    ... does say that any time a blur event happens in the job_form element, it will call a method on the view called assignValue. That method may interact with the model (it probably does, right?), but DOM events don't directly cause interaction with the model at all.

    If backbone views are really just jquery objects then aren't jquery listeners removed from DOM elements when the element backing the jquery object is removed from the DOM?

    Backbone's listeners are wholly different than jQuery listeners. They listen for Backbone-centric events. See here for the list of built-in events that Backbone components fire. A View's events hash is a nice convention that is used to listen for DOM events; it's basically a nice wrapper around the jQuery concept of event delegation.

    Do I only need to remove the listeners if I am going to not destroy the view entirely and save it for later use?

    If you don't remove listeners, they will continue to run whenever the related event happens, regardless of whether the listening component is changing the page. Suppose you had a Backbone.View that did something like this:

    var MyView = Backbone.View.extend({
        // ...
        events: {
            // Don't do this!
            'click': '_onClick'
        },
        // ...
    
        _onClick: function() {
             this.$el.append('Clicked!');
        }
     });
    

    Any time any click DOM event happens on the page, this view will append the text Clicked! to its internal DOM element. When the view is attached to the page's DOM, Clicked! would appear on every click. When the view was removed from the DOM, the function would still run on every click... but since the View's internal root element wasn't attached to anything the function would have no effect.

    It's a type of a memory leak, as any instance of MyView will ever be cleared up by the garbage collector. But the particularly nefarious side effect is it also uses CPU time to do something that is completely worthless. Now imagine if the event listener did anything of consequence. Performance of the page will suffer.