a paid nerd
a paid nerd

Reputation: 31502

Backbone.js - Given an element, how do I get the view?

I've created a bunch of Backbone.js views. Each view has an associated element (view.el).

Given an element on the page — out of context of the view — what would be the best way to get the view for the element?

For example, say some event affects a bunch of elements on a page and I want to call a method on every view associated with the affected elements.

One way would be to assign the view to the element's data, but I'm wondering if I've missed something smarter:

var myview = BackBone.View.extend({
    initialize: function(options) {
        $(this.el).data('view', this);
        ...
    }
});

(I'm using Backbone with jQuery 1.5.)

Upvotes: 31

Views: 16543

Answers (5)

Louis
Louis

Reputation: 151380

I've been using a method inspired by Ed's solution but it does not require the use of jQuery. It does two things:

  1. It sets the attribute data-backbone-view on the root elements of all views. This is convenient when looking at the DOM tree in a debugger. You can immediately see which elements are associated with views. You can also use the CSS selector [data-backbone-view] to find elements that are the roots of Backbone views.

  2. It adds a backboneView property to each root element of a view. It is then possible to get from the DOM element to the view by looking a the DOM element's properties.

I turn this on only when I'm debugging. Here is the code:

var originalSetElement = Bb.View.prototype.setElement;

Bb.View.prototype.setElement = function setElement(element) {
  if (this.el && this.el !== element) {
    delete this.el.backboneView;
  }

  element.backboneView = this;
  element.setAttribute("data-backbone-view", "true");

  return originalSetElement.apply(this, arguments);
};

Upvotes: 1

Ed .
Ed .

Reputation: 6403

I've just written a jQuery plugin for this. It also uses the .data() method.

Registration:

I have wrapped / proxied the Backbone View setElement method to attach the required data to the view's $el property.

Registration is done behind the scenes like so:

$(myViewsEl).backboneView(myView);

Retrieval:

The plugin traverses up the DOM hierarchy (using .closest()) until it finds an element with the required data entry, i.e a DOM element with an associated view:

var nearestView = $(e.target).backboneView();

In addition, we can specify what type of Backbone View we wish to obtain, continuing up the hierarchy until we find an instance of matching type:

var nearestButtonView = $(e.target).backboneView(ButtonView);

JSFiddle Example:

Can be found here.

Notes:

I hope I am correct in thinking there are no memory leaks involved here; An 'unlink' is performed if setElement is called a second time round, and since removing a view's element calls .remove() by default, which destroys all data as well. Let me know if you think differently.

The plugin code:

(function($) {

    // Proxy the original Backbone.View setElement method:
    // See: http://backbonejs.org/#View-setElement

    var backboneSetElementOriginal = Backbone.View.prototype.setElement;

    Backbone.View.prototype.setElement = function(element) {
        if (this.el != element) {
            $(this.el).backboneView('unlink');                    
        }

        $(element).backboneView(this);    

        return backboneSetElementOriginal.apply(this, arguments);
    };

    // Create a custom selector to search for the presence of a 'backboneView' data entry:
    // This avoids a dependency on a data selector plugin...

    $.expr[':'].backboneView = function(element, intStackIndex, arrProperties, arrNodeStack) {
        return $(element).data('backboneView') !== undefined;        
    };

    // Plugin internal functions:

    var registerViewToElement = function($el, view) {
        $el.data('backboneView', view);
    };

    var getClosestViewFromElement = function($el, viewType) {
        var ret = null;

        viewType = viewType || Backbone.View;

        while ($el.length) {
            $el = $el.closest(':backboneView');
            ret = $el.length ? $el.data('backboneView') : null;

            if (ret instanceof viewType) {
                break;
            }
            else {
                $el = $el.parent();
            }           
        }

        return ret;                
    };

    // Extra methods:

    var methods = {

        unlink: function($el) {
            $el.removeData('backboneView');        
        }

    };

    // Plugin:

    $.fn.backboneView = function() {
        var ret = this;
        var args = Array.prototype.slice.call(arguments, 0);

        if ($.isFunction(methods[args[0]])) {
            methods[args[0]](this);                        
        }
        else if (args[0] && args[0] instanceof Backbone.View) {
            registerViewToElement(this.first(), args[0]);                
        }
        else {
            ret = getClosestViewFromElement(this.first(), args[0]);
        }

        return ret;        
    }        

})(jQuery);

Upvotes: 15

M. Lanza
M. Lanza

Reputation: 6790

You could maintain a views hash (dictionary) that uses the element as the key and returns the view (or views).

http://www.timdown.co.uk/jshashtable/

Upvotes: 2

Bill Eisenhauer
Bill Eisenhauer

Reputation: 6183

Every view can register for DOM events. As such, every view with the kind of element that you are interested in should register for the DOM event and then assign an event-responding function that does what you want. If you need to DRY things up, use mixin techniques to mix in the function.

I think maybe this solution is easier than you may have initially imagined. Just let the views do the work that they are intended to do.

Upvotes: 6

Vlad Gurovich
Vlad Gurovich

Reputation: 8463

Since every view has a reference to the model its displaying, what I would do is assign id of the model to the view's associated element(hopefuly that is not affected by the changes by outside event). Also make sure that the model has a reference to its view. Then have these models stored in a Backbone collection.

With this setup, once something happens to an element, you use the elements id to retrieve corresponding model from Backbone collection that you created above and then this model will give you your view reference.

Upvotes: 0

Related Questions